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