• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2const common = require('../common');
3const assert = require('assert');
4const domain = require('domain');
5const { inspect } = require('util');
6
7common.disableCrashOnUnhandledRejection();
8
9const asyncTest = (function() {
10  let asyncTestsEnabled = false;
11  let asyncTestLastCheck;
12  const asyncTestQueue = [];
13  let asyncTestHandle;
14  let currentTest = null;
15
16  function fail(error) {
17    const stack = currentTest ?
18      `${inspect(error)}\nFrom previous event:\n${currentTest.stack}` :
19      inspect(error);
20
21    if (currentTest)
22      process.stderr.write(`'${currentTest.description}' failed\n\n`);
23
24    process.stderr.write(stack);
25    process.exit(2);
26  }
27
28  function nextAsyncTest() {
29    let called = false;
30    function done(err) {
31      if (called) return fail(new Error('done called twice'));
32      called = true;
33      asyncTestLastCheck = Date.now();
34      if (arguments.length > 0) return fail(err);
35      setTimeout(nextAsyncTest, 10);
36    }
37
38    if (asyncTestQueue.length) {
39      const test = asyncTestQueue.shift();
40      currentTest = test;
41      test.action(done);
42    } else {
43      clearInterval(asyncTestHandle);
44    }
45  }
46
47  return function asyncTest(description, fn) {
48    const stack = inspect(new Error()).split('\n').slice(1).join('\n');
49    asyncTestQueue.push({
50      action: fn,
51      stack,
52      description
53    });
54    if (!asyncTestsEnabled) {
55      asyncTestsEnabled = true;
56      asyncTestLastCheck = Date.now();
57      process.on('uncaughtException', fail);
58      asyncTestHandle = setInterval(function() {
59        const now = Date.now();
60        if (now - asyncTestLastCheck > 10000) {
61          return fail(new Error('Async test timeout exceeded'));
62        }
63      }, 10);
64      setTimeout(nextAsyncTest, 10);
65    }
66  };
67
68})();
69
70function setupException(fn) {
71  const listeners = process.listeners('uncaughtException');
72  process.removeAllListeners('uncaughtException');
73  process.on('uncaughtException', fn);
74  return function clean() {
75    process.removeListener('uncaughtException', fn);
76    listeners.forEach(function(listener) {
77      process.on('uncaughtException', listener);
78    });
79  };
80}
81
82function clean() {
83  process.removeAllListeners('unhandledRejection');
84  process.removeAllListeners('rejectionHandled');
85}
86
87function onUnhandledSucceed(done, predicate) {
88  clean();
89  process.on('unhandledRejection', function(reason, promise) {
90    try {
91      predicate(reason, promise);
92    } catch (e) {
93      return done(e);
94    }
95    done();
96  });
97}
98
99function onUnhandledFail(done) {
100  clean();
101  process.on('unhandledRejection', function(reason, promise) {
102    done(new Error('unhandledRejection not supposed to be triggered'));
103  });
104  process.on('rejectionHandled', function() {
105    done(new Error('rejectionHandled not supposed to be triggered'));
106  });
107  setTimeout(function() {
108    done();
109  }, 10);
110}
111
112asyncTest('synchronously rejected promise should trigger' +
113          ' unhandledRejection', function(done) {
114  const e = new Error();
115  onUnhandledSucceed(done, function(reason, promise) {
116    assert.strictEqual(reason, e);
117  });
118  Promise.reject(e);
119});
120
121asyncTest('synchronously rejected promise should trigger' +
122          ' unhandledRejection', function(done) {
123  const e = new Error();
124  onUnhandledSucceed(done, function(reason, promise) {
125    assert.strictEqual(reason, e);
126  });
127  new Promise(function(_, reject) {
128    reject(e);
129  });
130});
131
132asyncTest('Promise rejected after setImmediate should trigger' +
133          ' unhandledRejection', function(done) {
134  const e = new Error();
135  onUnhandledSucceed(done, function(reason, promise) {
136    assert.strictEqual(reason, e);
137  });
138  new Promise(function(_, reject) {
139    setImmediate(function() {
140      reject(e);
141    });
142  });
143});
144
145asyncTest('Promise rejected after setTimeout(,1) should trigger' +
146          ' unhandled rejection', function(done) {
147  const e = new Error();
148  onUnhandledSucceed(done, function(reason, promise) {
149    assert.strictEqual(reason, e);
150  });
151  new Promise(function(_, reject) {
152    setTimeout(function() {
153      reject(e);
154    }, 1);
155  });
156});
157
158asyncTest('Catching a promise rejection after setImmediate is not' +
159          ' soon enough to stop unhandledRejection', function(done) {
160  const e = new Error();
161  onUnhandledSucceed(done, function(reason, promise) {
162    assert.strictEqual(reason, e);
163  });
164  let _reject;
165  const promise = new Promise(function(_, reject) {
166    _reject = reject;
167  });
168  _reject(e);
169  setImmediate(function() {
170    promise.then(assert.fail, function() {});
171  });
172});
173
174asyncTest('When re-throwing new errors in a promise catch, only the' +
175          ' re-thrown error should hit unhandledRejection', function(done) {
176  const e = new Error();
177  const e2 = new Error();
178  onUnhandledSucceed(done, function(reason, promise) {
179    assert.strictEqual(reason, e2);
180    assert.strictEqual(promise, promise2);
181  });
182  const promise2 = Promise.reject(e).then(assert.fail, function(reason) {
183    assert.strictEqual(reason, e);
184    throw e2;
185  });
186});
187
188asyncTest('Test params of unhandledRejection for a synchronously-rejected ' +
189          'promise', function(done) {
190  const e = new Error();
191  onUnhandledSucceed(done, function(reason, promise) {
192    assert.strictEqual(reason, e);
193    assert.strictEqual(promise, promise);
194  });
195  Promise.reject(e);
196});
197
198asyncTest('When re-throwing new errors in a promise catch, only the ' +
199          're-thrown error should hit unhandledRejection: original promise' +
200          ' rejected async with setTimeout(,1)', function(done) {
201  const e = new Error();
202  const e2 = new Error();
203  onUnhandledSucceed(done, function(reason, promise) {
204    assert.strictEqual(reason, e2);
205    assert.strictEqual(promise, promise2);
206  });
207  const promise2 = new Promise(function(_, reject) {
208    setTimeout(function() {
209      reject(e);
210    }, 1);
211  }).then(assert.fail, function(reason) {
212    assert.strictEqual(reason, e);
213    throw e2;
214  });
215});
216
217asyncTest('When re-throwing new errors in a promise catch, only the re-thrown' +
218          ' error should hit unhandledRejection: promise catch attached a' +
219          ' process.nextTick after rejection', function(done) {
220  const e = new Error();
221  const e2 = new Error();
222  onUnhandledSucceed(done, function(reason, promise) {
223    assert.strictEqual(reason, e2);
224    assert.strictEqual(promise, promise2);
225  });
226  const promise = new Promise(function(_, reject) {
227    setTimeout(function() {
228      reject(e);
229      process.nextTick(function() {
230        promise2 = promise.then(assert.fail, function(reason) {
231          assert.strictEqual(reason, e);
232          throw e2;
233        });
234      });
235    }, 1);
236  });
237  let promise2;
238});
239
240asyncTest(
241  'unhandledRejection should not be triggered if a promise catch is' +
242  ' attached synchronously upon the promise\'s creation',
243  function(done) {
244    const e = new Error();
245    onUnhandledFail(done);
246    Promise.reject(e).then(assert.fail, function() {});
247  }
248);
249
250asyncTest(
251  'unhandledRejection should not be triggered if a promise catch is' +
252  ' attached synchronously upon the promise\'s creation',
253  function(done) {
254    const e = new Error();
255    onUnhandledFail(done);
256    new Promise(function(_, reject) {
257      reject(e);
258    }).then(assert.fail, function() {});
259  }
260);
261
262asyncTest('Attaching a promise catch in a process.nextTick is soon enough to' +
263          ' prevent unhandledRejection', function(done) {
264  const e = new Error();
265  onUnhandledFail(done);
266  const promise = Promise.reject(e);
267  process.nextTick(function() {
268    promise.then(assert.fail, function() {});
269  });
270});
271
272asyncTest('Attaching a promise catch in a process.nextTick is soon enough to' +
273          ' prevent unhandledRejection', function(done) {
274  const e = new Error();
275  onUnhandledFail(done);
276  const promise = new Promise(function(_, reject) {
277    reject(e);
278  });
279  process.nextTick(function() {
280    promise.then(assert.fail, function() {});
281  });
282});
283
284asyncTest('While inside setImmediate, catching a rejected promise derived ' +
285          'from returning a rejected promise in a fulfillment handler ' +
286          'prevents unhandledRejection', function(done) {
287  onUnhandledFail(done);
288
289  setImmediate(function() {
290    // Reproduces on first tick and inside of setImmediate
291    Promise
292      .resolve('resolve')
293      .then(function() {
294        return Promise.reject('reject');
295      }).catch(function(e) {});
296  });
297});
298
299// State adaptation tests
300asyncTest('catching a promise which is asynchronously rejected (via ' +
301          'resolution to an asynchronously-rejected promise) prevents' +
302          ' unhandledRejection', function(done) {
303  const e = new Error();
304  onUnhandledFail(done);
305  Promise.resolve().then(function() {
306    return new Promise(function(_, reject) {
307      setTimeout(function() {
308        reject(e);
309      }, 1);
310    });
311  }).then(assert.fail, function(reason) {
312    assert.strictEqual(reason, e);
313  });
314});
315
316asyncTest('Catching a rejected promise derived from throwing in a' +
317          ' fulfillment handler prevents unhandledRejection', function(done) {
318  const e = new Error();
319  onUnhandledFail(done);
320  Promise.resolve().then(function() {
321    throw e;
322  }).then(assert.fail, function(reason) {
323    assert.strictEqual(reason, e);
324  });
325});
326
327asyncTest('Catching a rejected promise derived from returning a' +
328          ' synchronously-rejected promise in a fulfillment handler' +
329          ' prevents unhandledRejection', function(done) {
330  const e = new Error();
331  onUnhandledFail(done);
332  Promise.resolve().then(function() {
333    return Promise.reject(e);
334  }).then(assert.fail, function(reason) {
335    assert.strictEqual(reason, e);
336  });
337});
338
339asyncTest('A rejected promise derived from returning an' +
340          ' asynchronously-rejected promise in a fulfillment handler' +
341          ' does trigger unhandledRejection', function(done) {
342  const e = new Error();
343  onUnhandledSucceed(done, function(reason, promise) {
344    assert.strictEqual(reason, e);
345    assert.strictEqual(promise, _promise);
346  });
347  const _promise = Promise.resolve().then(function() {
348    return new Promise(function(_, reject) {
349      setTimeout(function() {
350        reject(e);
351      }, 1);
352    });
353  });
354});
355
356asyncTest('A rejected promise derived from throwing in a fulfillment handler' +
357          ' does trigger unhandledRejection', function(done) {
358  const e = new Error();
359  onUnhandledSucceed(done, function(reason, promise) {
360    assert.strictEqual(reason, e);
361    assert.strictEqual(promise, _promise);
362  });
363  const _promise = Promise.resolve().then(function() {
364    throw e;
365  });
366});
367
368asyncTest(
369  'A rejected promise derived from returning a synchronously-rejected' +
370  ' promise in a fulfillment handler does trigger unhandledRejection',
371  function(done) {
372    const e = new Error();
373    onUnhandledSucceed(done, function(reason, promise) {
374      assert.strictEqual(reason, e);
375      assert.strictEqual(promise, _promise);
376    });
377    const _promise = Promise.resolve().then(function() {
378      return Promise.reject(e);
379    });
380  }
381);
382
383// Combinations with Promise.all
384asyncTest('Catching the Promise.all() of a collection that includes a ' +
385          'rejected promise prevents unhandledRejection', function(done) {
386  const e = new Error();
387  onUnhandledFail(done);
388  Promise.all([Promise.reject(e)]).then(assert.fail, function() {});
389});
390
391asyncTest(
392  'Catching the Promise.all() of a collection that includes a ' +
393  'nextTick-async rejected promise prevents unhandledRejection',
394  function(done) {
395    const e = new Error();
396    onUnhandledFail(done);
397    let p = new Promise(function(_, reject) {
398      process.nextTick(function() {
399        reject(e);
400      });
401    });
402    p = Promise.all([p]);
403    process.nextTick(function() {
404      p.then(assert.fail, function() {});
405    });
406  }
407);
408
409asyncTest('Failing to catch the Promise.all() of a collection that includes' +
410          ' a rejected promise triggers unhandledRejection for the returned' +
411          ' promise, not the passed promise', function(done) {
412  const e = new Error();
413  onUnhandledSucceed(done, function(reason, promise) {
414    assert.strictEqual(reason, e);
415    assert.strictEqual(promise, p);
416  });
417  const p = Promise.all([Promise.reject(e)]);
418});
419
420asyncTest('Waiting setTimeout(, 10) to catch a promise causes an' +
421          ' unhandledRejection + rejectionHandled pair', function(done) {
422  clean();
423  const unhandledPromises = [];
424  const e = new Error();
425  process.on('unhandledRejection', function(reason, promise) {
426    assert.strictEqual(reason, e);
427    unhandledPromises.push(promise);
428  });
429  process.on('rejectionHandled', function(promise) {
430    assert.strictEqual(unhandledPromises.length, 1);
431    assert.strictEqual(unhandledPromises[0], promise);
432    assert.strictEqual(promise, thePromise);
433    done();
434  });
435
436  const thePromise = new Promise(function() {
437    throw e;
438  });
439  setTimeout(function() {
440    thePromise.then(assert.fail, function(reason) {
441      assert.strictEqual(reason, e);
442    });
443  }, 10);
444});
445
446asyncTest('Waiting for some combination of process.nextTick + promise' +
447          ' microtasks to attach a catch handler is still soon enough to' +
448          ' prevent unhandledRejection', function(done) {
449  const e = new Error();
450  onUnhandledFail(done);
451
452
453  const a = Promise.reject(e);
454  process.nextTick(function() {
455    Promise.resolve().then(function() {
456      process.nextTick(function() {
457        Promise.resolve().then(function() {
458          a.catch(function() {});
459        });
460      });
461    });
462  });
463});
464
465asyncTest('Waiting for some combination of process.nextTick + promise' +
466          ' microtasks to attach a catch handler is still soon enough to ' +
467          'prevent unhandledRejection: inside setImmediate', function(done) {
468  const e = new Error();
469  onUnhandledFail(done);
470
471  setImmediate(function() {
472    const a = Promise.reject(e);
473    process.nextTick(function() {
474      Promise.resolve().then(function() {
475        process.nextTick(function() {
476          Promise.resolve().then(function() {
477            a.catch(function() {});
478          });
479        });
480      });
481    });
482  });
483});
484
485asyncTest('Waiting for some combination of process.nextTick + promise ' +
486          'microtasks to attach a catch handler is still soon enough to ' +
487          'prevent unhandledRejection: inside setTimeout', function(done) {
488  const e = new Error();
489  onUnhandledFail(done);
490
491  setTimeout(function() {
492    const a = Promise.reject(e);
493    process.nextTick(function() {
494      Promise.resolve().then(function() {
495        process.nextTick(function() {
496          Promise.resolve().then(function() {
497            a.catch(function() {});
498          });
499        });
500      });
501    });
502  }, 0);
503});
504
505asyncTest('Waiting for some combination of promise microtasks + ' +
506          'process.nextTick to attach a catch handler is still soon enough' +
507          ' to prevent unhandledRejection', function(done) {
508  const e = new Error();
509  onUnhandledFail(done);
510
511
512  const a = Promise.reject(e);
513  Promise.resolve().then(function() {
514    process.nextTick(function() {
515      Promise.resolve().then(function() {
516        process.nextTick(function() {
517          a.catch(function() {});
518        });
519      });
520    });
521  });
522});
523
524asyncTest(
525  'Waiting for some combination of promise microtasks +' +
526  ' process.nextTick to attach a catch handler is still soon enough' +
527  ' to prevent unhandledRejection: inside setImmediate',
528  function(done) {
529    const e = new Error();
530    onUnhandledFail(done);
531
532    setImmediate(function() {
533      const a = Promise.reject(e);
534      Promise.resolve().then(function() {
535        process.nextTick(function() {
536          Promise.resolve().then(function() {
537            process.nextTick(function() {
538              a.catch(function() {});
539            });
540          });
541        });
542      });
543    });
544  }
545);
546
547asyncTest('Waiting for some combination of promise microtasks +' +
548          ' process.nextTick to attach a catch handler is still soon enough' +
549          ' to prevent unhandledRejection: inside setTimeout', function(done) {
550  const e = new Error();
551  onUnhandledFail(done);
552
553  setTimeout(function() {
554    const a = Promise.reject(e);
555    Promise.resolve().then(function() {
556      process.nextTick(function() {
557        Promise.resolve().then(function() {
558          process.nextTick(function() {
559            a.catch(function() {});
560          });
561        });
562      });
563    });
564  }, 0);
565});
566
567asyncTest('setImmediate + promise microtasks is too late to attach a catch' +
568          ' handler; unhandledRejection will be triggered in that case.' +
569          ' (setImmediate before promise creation/rejection)', function(done) {
570  const e = new Error();
571  onUnhandledSucceed(done, function(reason, promise) {
572    assert.strictEqual(reason, e);
573    assert.strictEqual(promise, p);
574  });
575  const p = Promise.reject(e);
576  setImmediate(function() {
577    Promise.resolve().then(function() {
578      p.catch(function() {});
579    });
580  });
581});
582
583asyncTest('setImmediate + promise microtasks is too late to attach a catch' +
584          ' handler; unhandledRejection will be triggered in that case' +
585          ' (setImmediate before promise creation/rejection)', function(done) {
586  onUnhandledSucceed(done, function(reason, promise) {
587    assert.strictEqual(reason, undefined);
588    assert.strictEqual(promise, p);
589  });
590  setImmediate(function() {
591    Promise.resolve().then(function() {
592      Promise.resolve().then(function() {
593        Promise.resolve().then(function() {
594          Promise.resolve().then(function() {
595            p.catch(function() {});
596          });
597        });
598      });
599    });
600  });
601  const p = Promise.reject();
602});
603
604asyncTest('setImmediate + promise microtasks is too late to attach a catch' +
605          ' handler; unhandledRejection will be triggered in that case' +
606          ' (setImmediate after promise creation/rejection)', function(done) {
607  onUnhandledSucceed(done, function(reason, promise) {
608    assert.strictEqual(reason, undefined);
609    assert.strictEqual(promise, p);
610  });
611  const p = Promise.reject();
612  setImmediate(function() {
613    Promise.resolve().then(function() {
614      Promise.resolve().then(function() {
615        Promise.resolve().then(function() {
616          Promise.resolve().then(function() {
617            p.catch(function() {});
618          });
619        });
620      });
621    });
622  });
623});
624
625asyncTest(
626  'Promise unhandledRejection handler does not interfere with domain' +
627  ' error handlers being given exceptions thrown from nextTick.',
628  function(done) {
629    const d = domain.create();
630    let domainReceivedError;
631    d.on('error', function(e) {
632      domainReceivedError = e;
633    });
634    d.run(function() {
635      const e = new Error('error');
636      const domainError = new Error('domain error');
637      onUnhandledSucceed(done, function(reason, promise) {
638        assert.strictEqual(reason, e);
639        assert.strictEqual(domainReceivedError, domainError);
640      });
641      Promise.reject(e);
642      process.nextTick(function() {
643        throw domainError;
644      });
645    });
646  }
647);
648
649asyncTest('nextTick is immediately scheduled when called inside an event' +
650          ' handler', function(done) {
651  clean();
652  const e = new Error('error');
653  process.on('unhandledRejection', function(reason, promise) {
654    const order = [];
655    process.nextTick(function() {
656      order.push(1);
657    });
658    setTimeout(function() {
659      order.push(2);
660      assert.deepStrictEqual([1, 2], order);
661      done();
662    }, 1);
663  });
664  Promise.reject(e);
665});
666
667asyncTest('Throwing an error inside a rejectionHandled handler goes to' +
668          ' unhandledException, and does not cause .catch() to throw an ' +
669          'exception', function(done) {
670  clean();
671  const e = new Error();
672  const e2 = new Error();
673  const tearDownException = setupException(function(err) {
674    assert.strictEqual(err, e2);
675    tearDownException();
676    done();
677  });
678  process.on('rejectionHandled', function() {
679    throw e2;
680  });
681  const p = Promise.reject(e);
682  setTimeout(function() {
683    try {
684      p.catch(function() {});
685    } catch {
686      done(new Error('fail'));
687    }
688  }, 1);
689});
690
691asyncTest('Rejected promise inside unhandledRejection allows nextTick loop' +
692          ' to proceed first', function(done) {
693  clean();
694  Promise.reject(0);
695  let didCall = false;
696  process.on('unhandledRejection', () => {
697    assert(!didCall);
698    didCall = true;
699    const promise = Promise.reject(0);
700    process.nextTick(() => promise.catch(() => done()));
701  });
702});
703
704asyncTest(
705  'Unhandled promise rejection emits a warning immediately',
706  function(done) {
707    clean();
708    Promise.reject(0);
709    const { emitWarning } = process;
710    process.emitWarning = common.mustCall((...args) => {
711      if (timer) {
712        clearTimeout(timer);
713        timer = null;
714        done();
715      }
716      emitWarning(...args);
717    }, 2);
718
719    let timer = setTimeout(common.mustNotCall(), 10000);
720  },
721);
722
723// https://github.com/nodejs/node/issues/30953
724asyncTest(
725  'Catching a promise should not take effect on previous promises',
726  function(done) {
727    onUnhandledSucceed(done, function(reason, promise) {
728      assert.strictEqual(reason, '1');
729    });
730    Promise.reject('1');
731    Promise.reject('2').catch(function() {});
732  }
733);
734