mirror of
https://github.com/nodejs/node.git
synced 2025-05-12 12:41:27 +00:00

Remove the unnecessary microTasksTickObject for scheduling microtasks and instead use TickInfo to keep track of whether promise rejections exist that need to be emitted. Consequently allow the microtasks to execute on average fewer times, in more predictable manner than previously. Simplify unhandled & handled rejection tracking to do more in C++ to avoid needing to expose additional info in JS. When new unhandledRejections are emitted within an unhandledRejection handler, allow the event loop to proceed first instead. This means that if the end-user code handles all promise rejections on nextTick, rejections within unhandledRejection now won't spiral into an infinite loop. PR-URL: https://github.com/nodejs/node/pull/18207 Fixes: https://github.com/nodejs/node/issues/17913 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
700 lines
20 KiB
JavaScript
700 lines
20 KiB
JavaScript
'use strict';
|
|
require('../common');
|
|
const assert = require('assert');
|
|
const domain = require('domain');
|
|
|
|
const asyncTest = (function() {
|
|
let asyncTestsEnabled = false;
|
|
let asyncTestLastCheck;
|
|
const asyncTestQueue = [];
|
|
let asyncTestHandle;
|
|
let currentTest = null;
|
|
|
|
function fail(error) {
|
|
const 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() {
|
|
let 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) {
|
|
const test = asyncTestQueue.shift();
|
|
currentTest = test;
|
|
test.action(done);
|
|
} else {
|
|
clearInterval(asyncTestHandle);
|
|
}
|
|
}
|
|
|
|
return function asyncTest(description, fn) {
|
|
const 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() {
|
|
const now = Date.now();
|
|
if (now - asyncTestLastCheck > 10000) {
|
|
return fail(new Error('Async test timeout exceeded'));
|
|
}
|
|
}, 10);
|
|
setTimeout(nextAsyncTest, 10);
|
|
}
|
|
};
|
|
|
|
})();
|
|
|
|
function setupException(fn) {
|
|
const 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) {
|
|
const e = new Error();
|
|
onUnhandledSucceed(done, function(reason, promise) {
|
|
assert.strictEqual(e, reason);
|
|
});
|
|
Promise.reject(e);
|
|
});
|
|
|
|
asyncTest('synchronously rejected promise should trigger' +
|
|
' unhandledRejection', function(done) {
|
|
const 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) {
|
|
const 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) {
|
|
const 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) {
|
|
const e = new Error();
|
|
onUnhandledSucceed(done, function(reason, promise) {
|
|
assert.strictEqual(e, reason);
|
|
});
|
|
let _reject;
|
|
const 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) {
|
|
const e = new Error();
|
|
const e2 = new Error();
|
|
onUnhandledSucceed(done, function(reason, promise) {
|
|
assert.strictEqual(e2, reason);
|
|
assert.strictEqual(promise2, promise);
|
|
});
|
|
const 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) {
|
|
const e = new Error();
|
|
onUnhandledSucceed(done, function(reason, promise) {
|
|
assert.strictEqual(e, reason);
|
|
assert.strictEqual(promise, 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) {
|
|
const e = new Error();
|
|
const e2 = new Error();
|
|
onUnhandledSucceed(done, function(reason, promise) {
|
|
assert.strictEqual(e2, reason);
|
|
assert.strictEqual(promise2, promise);
|
|
});
|
|
const 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) {
|
|
const e = new Error();
|
|
const e2 = new Error();
|
|
onUnhandledSucceed(done, function(reason, promise) {
|
|
assert.strictEqual(e2, reason);
|
|
assert.strictEqual(promise2, promise);
|
|
});
|
|
const 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);
|
|
});
|
|
let promise2;
|
|
});
|
|
|
|
asyncTest(
|
|
'unhandledRejection should not be triggered if a promise catch is' +
|
|
' attached synchronously upon the promise\'s creation',
|
|
function(done) {
|
|
const 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) {
|
|
const 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) {
|
|
const e = new Error();
|
|
onUnhandledFail(done);
|
|
const 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) {
|
|
const e = new Error();
|
|
onUnhandledFail(done);
|
|
const promise = new Promise(function(_, reject) {
|
|
reject(e);
|
|
});
|
|
process.nextTick(function() {
|
|
promise.then(assert.fail, function() {});
|
|
});
|
|
});
|
|
|
|
asyncTest('While inside setImmediate, catching a rejected promise derived ' +
|
|
'from returning a rejected promise in a fulfillment handler ' +
|
|
'prevents unhandledRejection', function(done) {
|
|
onUnhandledFail(done);
|
|
|
|
setImmediate(function() {
|
|
// reproduces on first tick and inside of setImmediate
|
|
Promise
|
|
.resolve('resolve')
|
|
.then(function() {
|
|
return Promise.reject('reject');
|
|
}).catch(function(e) {});
|
|
});
|
|
});
|
|
|
|
// State adaptation tests
|
|
asyncTest('catching a promise which is asynchronously rejected (via ' +
|
|
'resolution to an asynchronously-rejected promise) prevents' +
|
|
' unhandledRejection', function(done) {
|
|
const 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) {
|
|
const 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) {
|
|
const 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) {
|
|
const e = new Error();
|
|
onUnhandledSucceed(done, function(reason, promise) {
|
|
assert.strictEqual(e, reason);
|
|
assert.strictEqual(_promise, promise);
|
|
});
|
|
const _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) {
|
|
const e = new Error();
|
|
onUnhandledSucceed(done, function(reason, promise) {
|
|
assert.strictEqual(e, reason);
|
|
assert.strictEqual(_promise, promise);
|
|
});
|
|
const _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) {
|
|
const e = new Error();
|
|
onUnhandledSucceed(done, function(reason, promise) {
|
|
assert.strictEqual(e, reason);
|
|
assert.strictEqual(_promise, promise);
|
|
});
|
|
const _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) {
|
|
const 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) {
|
|
const e = new Error();
|
|
onUnhandledFail(done);
|
|
let 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) {
|
|
const e = new Error();
|
|
onUnhandledSucceed(done, function(reason, promise) {
|
|
assert.strictEqual(e, reason);
|
|
assert.strictEqual(p, promise);
|
|
});
|
|
const p = Promise.all([Promise.reject(e)]);
|
|
});
|
|
|
|
asyncTest('Waiting setTimeout(, 10) to catch a promise causes an' +
|
|
' unhandledRejection + rejectionHandled pair', function(done) {
|
|
clean();
|
|
const unhandledPromises = [];
|
|
const 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();
|
|
});
|
|
|
|
const 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) {
|
|
const e = new Error();
|
|
onUnhandledFail(done);
|
|
|
|
|
|
const 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) {
|
|
const e = new Error();
|
|
onUnhandledFail(done);
|
|
|
|
setImmediate(function() {
|
|
const 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) {
|
|
const e = new Error();
|
|
onUnhandledFail(done);
|
|
|
|
setTimeout(function() {
|
|
const 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) {
|
|
const e = new Error();
|
|
onUnhandledFail(done);
|
|
|
|
|
|
const 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) {
|
|
const e = new Error();
|
|
onUnhandledFail(done);
|
|
|
|
setImmediate(function() {
|
|
const 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) {
|
|
const e = new Error();
|
|
onUnhandledFail(done);
|
|
|
|
setTimeout(function() {
|
|
const 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) {
|
|
const e = new Error();
|
|
onUnhandledSucceed(done, function(reason, promise) {
|
|
assert.strictEqual(e, reason);
|
|
assert.strictEqual(p, promise);
|
|
});
|
|
const 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() {});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
const 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);
|
|
});
|
|
const 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) {
|
|
const d = domain.create();
|
|
let domainReceivedError;
|
|
d.on('error', function(e) {
|
|
domainReceivedError = e;
|
|
});
|
|
d.run(function() {
|
|
const e = new Error('error');
|
|
const domainError = new Error('domain error');
|
|
onUnhandledSucceed(done, function(reason, promise) {
|
|
assert.strictEqual(reason, e);
|
|
assert.strictEqual(domainReceivedError, domainError);
|
|
});
|
|
Promise.reject(e);
|
|
process.nextTick(function() {
|
|
throw domainError;
|
|
});
|
|
});
|
|
}
|
|
);
|
|
|
|
asyncTest('nextTick is immediately scheduled when called inside an event' +
|
|
' handler', function(done) {
|
|
clean();
|
|
const e = new Error('error');
|
|
process.on('unhandledRejection', function(reason, promise) {
|
|
const order = [];
|
|
process.nextTick(function() {
|
|
order.push(1);
|
|
});
|
|
setTimeout(function() {
|
|
order.push(2);
|
|
assert.deepStrictEqual([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();
|
|
const e = new Error();
|
|
const e2 = new Error();
|
|
const tearDownException = setupException(function(err) {
|
|
assert.strictEqual(e2, err);
|
|
tearDownException();
|
|
done();
|
|
});
|
|
process.on('rejectionHandled', function() {
|
|
throw e2;
|
|
});
|
|
const p = Promise.reject(e);
|
|
setTimeout(function() {
|
|
try {
|
|
p.catch(function() {});
|
|
} catch (e) {
|
|
done(new Error('fail'));
|
|
}
|
|
}, 1);
|
|
});
|
|
|
|
asyncTest('Rejected promise inside unhandledRejection allows nextTick loop' +
|
|
' to proceed first', function(done) {
|
|
clean();
|
|
Promise.reject(0);
|
|
let didCall = false;
|
|
process.on('unhandledRejection', () => {
|
|
assert(!didCall);
|
|
didCall = true;
|
|
const promise = Promise.reject(0);
|
|
process.nextTick(() => promise.catch(() => done()));
|
|
});
|
|
});
|