0
0
mirror of https://github.com/nodejs/node.git synced 2024-12-01 16:10:02 +01:00
nodejs/test/parallel/test-promises-unhandled-rejections.js
Vladimir Kurchatkin a251657058 node: mark promises as handled as soon as possible
Fixes: https://github.com/nodejs/io.js/issues/1912
PR-URL: https://github.com/nodejs/io.js/pull/1952
Reviewed-By: Domenic Denicola <d@domenic.me>
Reviewed-By: Petka Antonov <petka_antonov@hotmail.com>
2015-06-13 00:24:24 +03:00

680 lines
20 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
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() {});
});
});
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 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);
});