var common = require('../common'); var assert = require('assert'); var domain = require('domain'); var asyncTest = (function() { var asyncTestsEnabled = false; var asyncTestLastCheck; var asyncTestQueue = []; var asyncTestHandle; var currentTest = null; function fail(error) { var stack = currentTest ? error.stack + '\nFrom previous event:\n' + currentTest.stack : error.stack; if (currentTest) process.stderr.write('\'' + currentTest.description + '\' failed\n\n'); process.stderr.write(stack); process.exit(2); } function nextAsyncTest() { var called = false; function done(err) { if (called) return fail(new Error('done called twice')); called = true; asyncTestLastCheck = Date.now(); if (arguments.length > 0) return fail(err); setTimeout(nextAsyncTest, 10); } if (asyncTestQueue.length) { var test = asyncTestQueue.shift(); currentTest = test; test.action(done); } else { clearInterval(asyncTestHandle); } } return function asyncTest(description, fn) { var stack = new Error().stack.split('\n').slice(1).join('\n'); asyncTestQueue.push({ action: fn, stack: stack, description: description }); if (!asyncTestsEnabled) { asyncTestsEnabled = true; asyncTestLastCheck = Date.now(); process.on('uncaughtException', fail); asyncTestHandle = setInterval(function() { var now = Date.now(); if (now - asyncTestLastCheck > 10000) { return fail(new Error('Async test timeout exceeded')); } }, 10); setTimeout(nextAsyncTest, 10); } }; })(); function setupException(fn) { var listeners = process.listeners('uncaughtException'); process.removeAllListeners('uncaughtException'); process.on('uncaughtException', fn); return function clean() { process.removeListener('uncaughtException', fn); listeners.forEach(function(listener) { process.on('uncaughtException', listener); }); }; } function clean() { process.removeAllListeners('unhandledRejection'); process.removeAllListeners('rejectionHandled'); } function onUnhandledSucceed(done, predicate) { clean(); process.on('unhandledRejection', function(reason, promise) { try { predicate(reason, promise); } catch (e) { return done(e); } done(); }); } function onUnhandledFail(done) { clean(); process.on('unhandledRejection', function(reason, promise) { done(new Error('unhandledRejection not supposed to be triggered')); }); process.on('rejectionHandled', function() { done(new Error('rejectionHandled not supposed to be triggered')); }); setTimeout(function() { done(); }, 10); } asyncTest('synchronously rejected promise should trigger unhandledRejection', function(done) { var e = new Error(); onUnhandledSucceed(done, function(reason, promise) { assert.strictEqual(e, reason); }); Promise.reject(e); }); asyncTest('synchronously rejected promise should trigger unhandledRejection', function(done) { var e = new Error(); onUnhandledSucceed(done, function(reason, promise) { assert.strictEqual(e, reason); }); new Promise(function(_, reject) { reject(e); }); }); asyncTest('Promise rejected after setImmediate should trigger unhandledRejection', function(done) { var e = new Error(); onUnhandledSucceed(done, function(reason, promise) { assert.strictEqual(e, reason); }); new Promise(function(_, reject) { setImmediate(function() { reject(e); }); }); }); asyncTest('Promise rejected after setTimeout(,1) should trigger unhandled rejection', function(done) { var e = new Error(); onUnhandledSucceed(done, function(reason, promise) { assert.strictEqual(e, reason); }); new Promise(function(_, reject) { setTimeout(function() { reject(e); }, 1); }); }); asyncTest('Catching a promise rejection after setImmediate is not soon enough to stop unhandledRejection', function(done) { var e = new Error(); onUnhandledSucceed(done, function(reason, promise) { assert.strictEqual(e, reason); }); var _reject; var promise = new Promise(function(_, reject) { _reject = reject; }) _reject(e); setImmediate(function() { promise.then(assert.fail, function(){}); }); }); asyncTest('When re-throwing new errors in a promise catch, only the re-thrown error should hit unhandledRejection', function(done) { var e = new Error(); var e2 = new Error(); onUnhandledSucceed(done, function(reason, promise) { assert.strictEqual(e2, reason); assert.strictEqual(promise2, promise); }); var promise2 = Promise.reject(e).then(assert.fail, function(reason) { assert.strictEqual(e, reason); throw e2; }); }); asyncTest('Test params of unhandledRejection for a synchronously-rejected promise', function(done) { var e = new Error(); var e2 = new Error(); onUnhandledSucceed(done, function(reason, promise) { assert.strictEqual(e, reason); assert.strictEqual(promise, promise); }); var promise = Promise.reject(e); }); asyncTest('When re-throwing new errors in a promise catch, only the re-thrown error should hit unhandledRejection: original promise rejected async with setTimeout(,1)', function(done) { var e = new Error(); var e2 = new Error(); onUnhandledSucceed(done, function(reason, promise) { assert.strictEqual(e2, reason); assert.strictEqual(promise2, promise); }); var promise2 = new Promise(function(_, reject) { setTimeout(function() { reject(e); }, 1); }).then(assert.fail, function(reason) { assert.strictEqual(e, reason); throw e2; }); }); asyncTest('When re-throwing new errors in a promise catch, only the re-thrown error should hit unhandledRejection: promise catch attached a process.nextTick after rejection', function(done) { var e = new Error(); var e2 = new Error(); onUnhandledSucceed(done, function(reason, promise) { assert.strictEqual(e2, reason); assert.strictEqual(promise2, promise); }); var promise = new Promise(function(_, reject) { setTimeout(function() { reject(e); process.nextTick(function() { promise2 = promise.then(assert.fail, function(reason) { assert.strictEqual(e, reason); throw e2; }); }); }, 1); }); var promise2; }); asyncTest('unhandledRejection should not be triggered if a promise catch is attached synchronously upon the promise\'s creation', function(done) { var e = new Error(); onUnhandledFail(done); Promise.reject(e).then(assert.fail, function(){}); }); asyncTest('unhandledRejection should not be triggered if a promise catch is attached synchronously upon the promise\'s creation', function(done) { var e = new Error(); onUnhandledFail(done); new Promise(function(_, reject) { reject(e); }).then(assert.fail, function(){}); }); asyncTest('Attaching a promise catch in a process.nextTick is soon enough to prevent unhandledRejection', function(done) { var e = new Error(); onUnhandledFail(done); var promise = Promise.reject(e); process.nextTick(function() { promise.then(assert.fail, function(){}); }); }); asyncTest('Attaching a promise catch in a process.nextTick is soon enough to prevent unhandledRejection', function(done) { var e = new Error(); onUnhandledFail(done); var promise = new Promise(function(_, reject) { reject(e); }); process.nextTick(function() { promise.then(assert.fail, function(){}); }); }); // State adapation tests asyncTest('catching a promise which is asynchronously rejected (via resolution to an asynchronously-rejected promise) prevents unhandledRejection', function(done) { var e = new Error(); onUnhandledFail(done); Promise.resolve().then(function() { return new Promise(function(_, reject) { setTimeout(function() { reject(e); }, 1); }); }).then(assert.fail, function(reason) { assert.strictEqual(e, reason); }); }); asyncTest('Catching a rejected promise derived from throwing in a fulfillment handler prevents unhandledRejection', function(done) { var e = new Error(); onUnhandledFail(done); Promise.resolve().then(function() { throw e; }).then(assert.fail, function(reason) { assert.strictEqual(e, reason); }); }); asyncTest('Catching a rejected promise derived from returning a synchronously-rejected promise in a fulfillment handler prevents unhandledRejection', function(done) { var e = new Error(); onUnhandledFail(done); Promise.resolve().then(function() { return Promise.reject(e); }).then(assert.fail, function(reason) { assert.strictEqual(e, reason); }); }); asyncTest('A rejected promise derived from returning an asynchronously-rejected promise in a fulfillment handler does trigger unhandledRejection', function(done) { var e = new Error(); var _promise; onUnhandledSucceed(done, function(reason, promise) { assert.strictEqual(e, reason); assert.strictEqual(_promise, promise); }); _promise = Promise.resolve().then(function() { return new Promise(function(_, reject) { setTimeout(function() { reject(e); }, 1); }); }); }); asyncTest('A rejected promise derived from throwing in a fulfillment handler does trigger unhandledRejection', function(done) { var e = new Error(); var _promise; onUnhandledSucceed(done, function(reason, promise) { assert.strictEqual(e, reason); assert.strictEqual(_promise, promise); }); _promise = Promise.resolve().then(function() { throw e; }); }); asyncTest('A rejected promise derived from returning a synchronously-rejected promise in a fulfillment handler does trigger unhandledRejection', function(done) { var e = new Error(); var _promise; onUnhandledSucceed(done, function(reason, promise) { assert.strictEqual(e, reason); assert.strictEqual(_promise, promise); }); _promise = Promise.resolve().then(function() { return Promise.reject(e); }); }); // Combinations with Promise.all asyncTest('Catching the Promise.all() of a collection that includes a rejected promise prevents unhandledRejection', function(done) { var e = new Error(); onUnhandledFail(done); Promise.all([Promise.reject(e)]).then(assert.fail, function() {}); }); asyncTest('Catching the Promise.all() of a collection that includes a nextTick-async rejected promise prevents unhandledRejection', function(done) { var e = new Error(); onUnhandledFail(done); var p = new Promise(function(_, reject) { process.nextTick(function() { reject(e); }); }); p = Promise.all([p]); process.nextTick(function() { p.then(assert.fail, function() {}); }); }); asyncTest('Failing to catch the Promise.all() of a collection that includes a rejected promise triggers unhandledRejection for the returned promise, not the passed promise', function(done) { var e = new Error(); onUnhandledSucceed(done, function(reason, promise) { assert.strictEqual(e, reason); assert.strictEqual(p, promise); }); var p = Promise.all([Promise.reject(e)]); }); asyncTest('Waiting setTimeout(, 10) to catch a promise causes an unhandledRejection + rejectionHandled pair', function(done) { clean(); var unhandledPromises = []; var e = new Error(); process.on('unhandledRejection', function(reason, promise) { assert.strictEqual(e, reason); unhandledPromises.push(promise); }); process.on('rejectionHandled', function(promise) { assert.strictEqual(1, unhandledPromises.length); assert.strictEqual(unhandledPromises[0], promise); assert.strictEqual(thePromise, promise); done(); }); var thePromise = new Promise(function() { throw e; }); setTimeout(function() { thePromise.then(assert.fail, function(reason) { assert.strictEqual(e, reason); }); }, 10); }); asyncTest('Waiting for some combination of process.nextTick + promise microtasks to attach a catch handler is still soon enough to prevent unhandledRejection', function(done) { var e = new Error(); onUnhandledFail(done); var a = Promise.reject(e); process.nextTick(function() { Promise.resolve().then(function() { process.nextTick(function() { Promise.resolve().then(function() { a.catch(function() {}); }); }); }); }); }); asyncTest('Waiting for some combination of process.nextTick + promise microtasks to attach a catch handler is still soon enough to prevent unhandledRejection: inside setImmediate', function(done) { var e = new Error(); onUnhandledFail(done); setImmediate(function() { var a = Promise.reject(e); process.nextTick(function() { Promise.resolve().then(function() { process.nextTick(function() { Promise.resolve().then(function() { a.catch(function() {}); }); }); }); }); }); }); asyncTest('Waiting for some combination of process.nextTick + promise microtasks to attach a catch handler is still soon enough to prevent unhandledRejection: inside setTimeout', function(done) { var e = new Error(); onUnhandledFail(done); setTimeout(function() { var a = Promise.reject(e); process.nextTick(function() { Promise.resolve().then(function() { process.nextTick(function() { Promise.resolve().then(function() { a.catch(function() {}); }); }); }); }); }, 0); }); asyncTest('Waiting for some combination of promise microtasks + process.nextTick to attach a catch handler is still soon enough to prevent unhandledRejection', function(done) { var e = new Error(); onUnhandledFail(done); var a = Promise.reject(e); Promise.resolve().then(function() { process.nextTick(function() { Promise.resolve().then(function() { process.nextTick(function() { a.catch(function() {}); }); }); }); }); }); asyncTest('Waiting for some combination of promise microtasks + process.nextTick to attach a catch handler is still soon enough to prevent unhandledRejection: inside setImmediate', function(done) { var e = new Error(); onUnhandledFail(done); setImmediate(function() { var a = Promise.reject(e); Promise.resolve().then(function() { process.nextTick(function() { Promise.resolve().then(function() { process.nextTick(function() { a.catch(function() {}); }); }); }); }); }); }); asyncTest('Waiting for some combination of promise microtasks + process.nextTick to attach a catch handler is still soon enough to prevent unhandledRejection: inside setTimeout', function(done) { var e = new Error(); onUnhandledFail(done); setTimeout(function() { var a = Promise.reject(e); Promise.resolve().then(function() { process.nextTick(function() { Promise.resolve().then(function() { process.nextTick(function() { a.catch(function() {}); }); }); }); }); }, 0); }); asyncTest('setImmediate + promise microtasks is too late to attach a catch handler; unhandledRejection will be triggered in that case. (setImmediate before promise creation/rejection)', function(done) { var e = new Error(); onUnhandledSucceed(done, function(reason, promise) { assert.strictEqual(e, reason); assert.strictEqual(p, promise); }); var p = Promise.reject(e); setImmediate(function() { Promise.resolve().then(function () { p.catch(function(){}); }); }); }); asyncTest('setImmediate + promise microtasks is too late to attach a catch handler; unhandledRejection will be triggered in that case (setImmediate before promise creation/rejection)', function(done) { onUnhandledSucceed(done, function(reason, promise) { assert.strictEqual(undefined, reason); assert.strictEqual(p, promise); }); setImmediate(function() { Promise.resolve().then(function () { Promise.resolve().then(function () { Promise.resolve().then(function () { Promise.resolve().then(function () { p.catch(function(){}); }); }); }); }); }); var p = Promise.reject(); }); asyncTest('setImmediate + promise microtasks is too late to attach a catch handler; unhandledRejection will be triggered in that case (setImmediate after promise creation/rejection)', function(done) { onUnhandledSucceed(done, function(reason, promise) { assert.strictEqual(undefined, reason); assert.strictEqual(p, promise); }); var p = Promise.reject(); setImmediate(function() { Promise.resolve().then(function () { Promise.resolve().then(function () { Promise.resolve().then(function () { Promise.resolve().then(function () { p.catch(function(){}); }); }); }); }); }); }); asyncTest('Promise unhandledRejection handler does not interfere with domain error handlers being given exceptions thrown from nextTick.', function(done) { var d = domain.create(); var domainReceivedError; d.on('error', function(e) { domainReceivedError = e; }); d.run(function() { var e = new Error('error'); var domainError = new Error('domain error'); onUnhandledSucceed(done, function(reason, promise) { assert.strictEqual(reason, e); assert.strictEqual(domainReceivedError, domainError); d.dispose(); }); var a = Promise.reject(e); process.nextTick(function() { throw domainError; }); }); }); asyncTest('nextTick is immediately scheduled when called inside an event handler', function(done) { clean(); var e = new Error('error'); process.on('unhandledRejection', function(reason, promise) { var order = []; process.nextTick(function() { order.push(1); }); setTimeout(function() { order.push(2); assert.deepEqual([1,2], order); done(); }, 1); }); Promise.reject(e); }); asyncTest('Throwing an error inside a rejectionHandled handler goes to unhandledException, and does not cause .catch() to throw an exception', function(done) { clean(); var e = new Error(); var e2 = new Error(); var tearDownException = setupException(function(err) { assert.equal(e2, err); tearDownException(); done(); }); process.on('rejectionHandled', function() { throw e2; }); var p = Promise.reject(e); setTimeout(function() { try { p.catch(function(){}); } catch (e) { done(new Error('fail')); } }, 1); });