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