|
|
|
|
@@ -4,8 +4,8 @@ const {
|
|
|
|
|
ArrayPrototypePush,
|
|
|
|
|
ArrayPrototypeShift,
|
|
|
|
|
Error,
|
|
|
|
|
ObjectDefineProperty,
|
|
|
|
|
ObjectPrototypeHasOwnProperty,
|
|
|
|
|
SafeMap,
|
|
|
|
|
SafeWeakMap,
|
|
|
|
|
} = primordials;
|
|
|
|
|
|
|
|
|
|
@@ -14,8 +14,8 @@ const {
|
|
|
|
|
promiseRejectEvents: {
|
|
|
|
|
kPromiseRejectWithNoHandler,
|
|
|
|
|
kPromiseHandlerAddedAfterReject,
|
|
|
|
|
kPromiseResolveAfterResolved,
|
|
|
|
|
kPromiseRejectAfterResolved,
|
|
|
|
|
kPromiseResolveAfterResolved,
|
|
|
|
|
},
|
|
|
|
|
setPromiseRejectCallback,
|
|
|
|
|
} = internalBinding('task_queue');
|
|
|
|
|
@@ -41,88 +41,163 @@ const { isErrorStackTraceLimitWritable } = require('internal/errors');
|
|
|
|
|
// *Must* match Environment::TickInfo::Fields in src/env.h.
|
|
|
|
|
const kHasRejectionToWarn = 1;
|
|
|
|
|
|
|
|
|
|
// By default true because in cases where process is not a global
|
|
|
|
|
// it is not possible to determine if the user has added a listener
|
|
|
|
|
// to the process object.
|
|
|
|
|
let hasMultipleResolvesListener = true;
|
|
|
|
|
|
|
|
|
|
if (process.on) {
|
|
|
|
|
hasMultipleResolvesListener = process.listenerCount('multipleResolves') !== 0;
|
|
|
|
|
|
|
|
|
|
process.on('newListener', (eventName) => {
|
|
|
|
|
if (eventName === 'multipleResolves') {
|
|
|
|
|
hasMultipleResolvesListener = true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
process.on('removeListener', (eventName) => {
|
|
|
|
|
if (eventName === 'multipleResolves') {
|
|
|
|
|
hasMultipleResolvesListener = process.listenerCount('multipleResolves') !== 0;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Errors & Warnings
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
class UnhandledPromiseRejection extends Error {
|
|
|
|
|
code = 'ERR_UNHANDLED_REJECTION';
|
|
|
|
|
name = 'UnhandledPromiseRejection';
|
|
|
|
|
/**
|
|
|
|
|
* @param {Error} reason
|
|
|
|
|
*/
|
|
|
|
|
constructor(reason) {
|
|
|
|
|
super('This error originated either by throwing inside of an ' +
|
|
|
|
|
'async function without a catch block, or by rejecting a promise which ' +
|
|
|
|
|
'was not handled with .catch(). The promise rejected with the reason "' +
|
|
|
|
|
noSideEffectsToString(reason) + '".');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class UnhandledPromiseRejectionWarning extends Error {
|
|
|
|
|
name = 'UnhandledPromiseRejectionWarning';
|
|
|
|
|
/**
|
|
|
|
|
* @param {number} uid
|
|
|
|
|
*/
|
|
|
|
|
constructor(uid) {
|
|
|
|
|
const message = 'Unhandled promise rejection. This error originated either by ' +
|
|
|
|
|
'throwing inside of an async function without a catch block, ' +
|
|
|
|
|
'or by rejecting a promise which was not handled with .catch(). ' +
|
|
|
|
|
'To terminate the node process on unhandled promise ' +
|
|
|
|
|
'rejection, use the CLI flag `--unhandled-rejections=strict` (see ' +
|
|
|
|
|
'https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). ' +
|
|
|
|
|
`(rejection id: ${uid})`;
|
|
|
|
|
|
|
|
|
|
// UnhandledPromiseRejectionWarning will get the stack trace from the
|
|
|
|
|
// reason, so we can disable the stack trace limit temporarily for better
|
|
|
|
|
// performance.
|
|
|
|
|
if (isErrorStackTraceLimitWritable()) {
|
|
|
|
|
const stackTraceLimit = Error.stackTraceLimit;
|
|
|
|
|
Error.stackTraceLimit = 0;
|
|
|
|
|
super(message);
|
|
|
|
|
Error.stackTraceLimit = stackTraceLimit;
|
|
|
|
|
} else {
|
|
|
|
|
super(message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class PromiseRejectionHandledWarning extends Error {
|
|
|
|
|
name = 'PromiseRejectionHandledWarning';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {number} uid
|
|
|
|
|
*/
|
|
|
|
|
constructor(uid) {
|
|
|
|
|
super(`Promise rejection was handled asynchronously (rejection id: ${uid})`);
|
|
|
|
|
this.id = uid;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @typedef PromiseInfo
|
|
|
|
|
* @property {*} reason the reason for the rejection
|
|
|
|
|
* @property {number} uid the unique id of the promise
|
|
|
|
|
* @property {boolean} warned whether the rejection has been warned
|
|
|
|
|
* @property {object} [domain] the domain the promise was created in
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @type {WeakMap<Promise, PromiseInfo>}
|
|
|
|
|
*/
|
|
|
|
|
const maybeUnhandledPromises = new SafeWeakMap();
|
|
|
|
|
const pendingUnhandledRejections = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Using a Mp causes the promise to be referenced at least for one tick.
|
|
|
|
|
* @type {Map<Promise, PromiseInfo>}
|
|
|
|
|
*/
|
|
|
|
|
let pendingUnhandledRejections = new SafeMap();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @type {Array<{promise: Promise, warning: Error}>}
|
|
|
|
|
*/
|
|
|
|
|
const asyncHandledRejections = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @type {number}
|
|
|
|
|
*/
|
|
|
|
|
let lastPromiseId = 0;
|
|
|
|
|
|
|
|
|
|
// --unhandled-rejections=none:
|
|
|
|
|
// Emit 'unhandledRejection', but do not emit any warning.
|
|
|
|
|
const kIgnoreUnhandledRejections = 0;
|
|
|
|
|
|
|
|
|
|
// --unhandled-rejections=warn:
|
|
|
|
|
// Emit 'unhandledRejection', then emit 'UnhandledPromiseRejectionWarning'.
|
|
|
|
|
const kAlwaysWarnUnhandledRejections = 1;
|
|
|
|
|
|
|
|
|
|
// --unhandled-rejections=strict:
|
|
|
|
|
// Emit 'uncaughtException'. If it's not handled, print the error to stderr
|
|
|
|
|
// and exit the process.
|
|
|
|
|
// Otherwise, emit 'unhandledRejection'. If 'unhandledRejection' is not
|
|
|
|
|
// handled, emit 'UnhandledPromiseRejectionWarning'.
|
|
|
|
|
const kStrictUnhandledRejections = 2;
|
|
|
|
|
|
|
|
|
|
// --unhandled-rejections=throw:
|
|
|
|
|
// Emit 'unhandledRejection', if it's unhandled, emit
|
|
|
|
|
// 'uncaughtException'. If it's not handled, print the error to stderr
|
|
|
|
|
// and exit the process.
|
|
|
|
|
const kThrowUnhandledRejections = 3;
|
|
|
|
|
|
|
|
|
|
// --unhandled-rejections=warn-with-error-code:
|
|
|
|
|
// Emit 'unhandledRejection', if it's unhandled, emit
|
|
|
|
|
// 'UnhandledPromiseRejectionWarning', then set process exit code to 1.
|
|
|
|
|
|
|
|
|
|
const kWarnWithErrorCodeUnhandledRejections = 4;
|
|
|
|
|
|
|
|
|
|
let unhandledRejectionsMode;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {boolean} value
|
|
|
|
|
*/
|
|
|
|
|
function setHasRejectionToWarn(value) {
|
|
|
|
|
tickInfo[kHasRejectionToWarn] = value ? 1 : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
function hasRejectionToWarn() {
|
|
|
|
|
return tickInfo[kHasRejectionToWarn] === 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isErrorLike(o) {
|
|
|
|
|
return typeof o === 'object' &&
|
|
|
|
|
o !== null &&
|
|
|
|
|
ObjectPrototypeHasOwnProperty(o, 'stack');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getUnhandledRejectionsMode() {
|
|
|
|
|
const { getOptionValue } = require('internal/options');
|
|
|
|
|
switch (getOptionValue('--unhandled-rejections')) {
|
|
|
|
|
case 'none':
|
|
|
|
|
return kIgnoreUnhandledRejections;
|
|
|
|
|
case 'warn':
|
|
|
|
|
return kAlwaysWarnUnhandledRejections;
|
|
|
|
|
case 'strict':
|
|
|
|
|
return kStrictUnhandledRejections;
|
|
|
|
|
case 'throw':
|
|
|
|
|
return kThrowUnhandledRejections;
|
|
|
|
|
case 'warn-with-error-code':
|
|
|
|
|
return kWarnWithErrorCodeUnhandledRejections;
|
|
|
|
|
default:
|
|
|
|
|
return kThrowUnhandledRejections;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* @param {string|Error} obj
|
|
|
|
|
* @returns {obj is Error}
|
|
|
|
|
*/
|
|
|
|
|
function isErrorLike(obj) {
|
|
|
|
|
return typeof obj === 'object' &&
|
|
|
|
|
obj !== null &&
|
|
|
|
|
ObjectPrototypeHasOwnProperty(obj, 'stack');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {0|1|2|3} type
|
|
|
|
|
* @param {Promise} promise
|
|
|
|
|
* @param {Error} reason
|
|
|
|
|
*/
|
|
|
|
|
function promiseRejectHandler(type, promise, reason) {
|
|
|
|
|
if (unhandledRejectionsMode === undefined) {
|
|
|
|
|
unhandledRejectionsMode = getUnhandledRejectionsMode();
|
|
|
|
|
}
|
|
|
|
|
switch (type) {
|
|
|
|
|
case kPromiseRejectWithNoHandler:
|
|
|
|
|
case kPromiseRejectWithNoHandler: // 0
|
|
|
|
|
unhandledRejection(promise, reason);
|
|
|
|
|
break;
|
|
|
|
|
case kPromiseHandlerAddedAfterReject:
|
|
|
|
|
case kPromiseHandlerAddedAfterReject: // 1
|
|
|
|
|
handledRejection(promise);
|
|
|
|
|
break;
|
|
|
|
|
case kPromiseResolveAfterResolved:
|
|
|
|
|
resolveError('resolve', promise, reason);
|
|
|
|
|
case kPromiseRejectAfterResolved: // 2
|
|
|
|
|
if (hasMultipleResolvesListener) {
|
|
|
|
|
resolveErrorReject(promise, reason);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case kPromiseRejectAfterResolved:
|
|
|
|
|
resolveError('reject', promise, reason);
|
|
|
|
|
case kPromiseResolveAfterResolved: // 3
|
|
|
|
|
if (hasMultipleResolvesListener) {
|
|
|
|
|
resolveErrorResolve(promise, reason);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -132,69 +207,91 @@ const multipleResolvesDeprecate = deprecate(
|
|
|
|
|
'The multipleResolves event has been deprecated.',
|
|
|
|
|
'DEP0160',
|
|
|
|
|
);
|
|
|
|
|
function resolveError(type, promise, reason) {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {Promise} promise
|
|
|
|
|
* @param {Error} reason
|
|
|
|
|
*/
|
|
|
|
|
function resolveErrorResolve(promise, reason) {
|
|
|
|
|
// We have to wrap this in a next tick. Otherwise the error could be caught by
|
|
|
|
|
// the executed promise.
|
|
|
|
|
process.nextTick(() => {
|
|
|
|
|
if (process.emit('multipleResolves', type, promise, reason)) {
|
|
|
|
|
// Emit the multipleResolves event.
|
|
|
|
|
// This is a deprecated event, so we have to check if it's being listened to.
|
|
|
|
|
if (process.emit('multipleResolves', 'resolve', promise, reason)) {
|
|
|
|
|
// If the event is being listened to, emit a deprecation warning.
|
|
|
|
|
multipleResolvesDeprecate();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function unhandledRejection(promise, reason) {
|
|
|
|
|
const emit = (reason, promise, promiseInfo) => {
|
|
|
|
|
if (promiseInfo.domain) {
|
|
|
|
|
return promiseInfo.domain.emit('error', reason);
|
|
|
|
|
/**
|
|
|
|
|
* @param {Promise} promise
|
|
|
|
|
* @param {Error} reason
|
|
|
|
|
*/
|
|
|
|
|
function resolveErrorReject(promise, reason) {
|
|
|
|
|
// We have to wrap this in a next tick. Otherwise the error could be caught by
|
|
|
|
|
// the executed promise.
|
|
|
|
|
process.nextTick(() => {
|
|
|
|
|
if (process.emit('multipleResolves', 'reject', promise, reason)) {
|
|
|
|
|
multipleResolvesDeprecate();
|
|
|
|
|
}
|
|
|
|
|
return process.emit('unhandledRejection', reason, promise);
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
maybeUnhandledPromises.set(promise, {
|
|
|
|
|
/**
|
|
|
|
|
* @param {Promise} promise
|
|
|
|
|
* @param {PromiseInfo} promiseInfo
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
const emitUnhandledRejection = (promise, promiseInfo) => {
|
|
|
|
|
return promiseInfo.domain ?
|
|
|
|
|
promiseInfo.domain.emit('error', promiseInfo.reason) :
|
|
|
|
|
process.emit('unhandledRejection', promiseInfo.reason, promise);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {Promise} promise
|
|
|
|
|
* @param {Error} reason
|
|
|
|
|
*/
|
|
|
|
|
function unhandledRejection(promise, reason) {
|
|
|
|
|
pendingUnhandledRejections.set(promise, {
|
|
|
|
|
reason,
|
|
|
|
|
uid: ++lastPromiseId,
|
|
|
|
|
warned: false,
|
|
|
|
|
domain: process.domain,
|
|
|
|
|
emit,
|
|
|
|
|
});
|
|
|
|
|
// This causes the promise to be referenced at least for one tick.
|
|
|
|
|
ArrayPrototypePush(pendingUnhandledRejections, promise);
|
|
|
|
|
setHasRejectionToWarn(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {Promise} promise
|
|
|
|
|
*/
|
|
|
|
|
function handledRejection(promise) {
|
|
|
|
|
if (pendingUnhandledRejections.has(promise)) {
|
|
|
|
|
pendingUnhandledRejections.delete(promise);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const promiseInfo = maybeUnhandledPromises.get(promise);
|
|
|
|
|
if (promiseInfo !== undefined) {
|
|
|
|
|
maybeUnhandledPromises.delete(promise);
|
|
|
|
|
if (promiseInfo.warned) {
|
|
|
|
|
const { uid } = promiseInfo;
|
|
|
|
|
// Generate the warning object early to get a good stack trace.
|
|
|
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
|
|
|
const warning = new Error('Promise rejection was handled ' +
|
|
|
|
|
`asynchronously (rejection id: ${uid})`);
|
|
|
|
|
warning.name = 'PromiseRejectionHandledWarning';
|
|
|
|
|
warning.id = uid;
|
|
|
|
|
const warning = new PromiseRejectionHandledWarning(promiseInfo.uid);
|
|
|
|
|
ArrayPrototypePush(asyncHandledRejections, { promise, warning });
|
|
|
|
|
setHasRejectionToWarn(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (maybeUnhandledPromises.size === 0 && asyncHandledRejections.length === 0)
|
|
|
|
|
setHasRejectionToWarn(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const unhandledRejectionErrName = 'UnhandledPromiseRejectionWarning';
|
|
|
|
|
function emitUnhandledRejectionWarning(uid, reason) {
|
|
|
|
|
const warning = getErrorWithoutStack(
|
|
|
|
|
unhandledRejectionErrName,
|
|
|
|
|
'Unhandled promise rejection. This error originated either by ' +
|
|
|
|
|
'throwing inside of an async function without a catch block, ' +
|
|
|
|
|
'or by rejecting a promise which was not handled with .catch(). ' +
|
|
|
|
|
'To terminate the node process on unhandled promise ' +
|
|
|
|
|
'rejection, use the CLI flag `--unhandled-rejections=strict` (see ' +
|
|
|
|
|
'https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). ' +
|
|
|
|
|
`(rejection id: ${uid})`,
|
|
|
|
|
);
|
|
|
|
|
const unhandledRejectionErrName = UnhandledPromiseRejectionWarning.name;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {PromiseInfo} promiseInfo
|
|
|
|
|
*/
|
|
|
|
|
function emitUnhandledRejectionWarning(promiseInfo) {
|
|
|
|
|
const warning = new UnhandledPromiseRejectionWarning(promiseInfo.uid);
|
|
|
|
|
const reason = promiseInfo.reason;
|
|
|
|
|
try {
|
|
|
|
|
if (isErrorLike(reason)) {
|
|
|
|
|
warning.stack = reason.stack;
|
|
|
|
|
@@ -215,137 +312,177 @@ function emitUnhandledRejectionWarning(uid, reason) {
|
|
|
|
|
process.emitWarning(warning);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @callback UnhandledRejectionsModeHandler
|
|
|
|
|
* @param {Promise} promise
|
|
|
|
|
* @param {PromiseInfo} promiseInfo
|
|
|
|
|
* @param {number} [promiseAsyncId]
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The mode of unhandled rejections.
|
|
|
|
|
* @type {UnhandledRejectionsModeHandler}
|
|
|
|
|
*/
|
|
|
|
|
let unhandledRejectionsMode;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* --unhandled-rejections=strict:
|
|
|
|
|
* Emit 'uncaughtException'. If it's not handled, print the error to stderr
|
|
|
|
|
* and exit the process.
|
|
|
|
|
* Otherwise, emit 'unhandledRejection'. If 'unhandledRejection' is not
|
|
|
|
|
* handled, emit 'UnhandledPromiseRejectionWarning'.
|
|
|
|
|
* @type {UnhandledRejectionsModeHandler}
|
|
|
|
|
*/
|
|
|
|
|
function strictUnhandledRejectionsMode(promise, promiseInfo, promiseAsyncId) {
|
|
|
|
|
const reason = promiseInfo.reason;
|
|
|
|
|
const err = isErrorLike(reason) ?
|
|
|
|
|
reason : new UnhandledPromiseRejection(reason);
|
|
|
|
|
// This destroys the async stack, don't clear it after
|
|
|
|
|
triggerUncaughtException(err, true /* fromPromise */);
|
|
|
|
|
if (promiseAsyncId === undefined) {
|
|
|
|
|
pushAsyncContext(
|
|
|
|
|
promise[kAsyncIdSymbol],
|
|
|
|
|
promise[kTriggerAsyncIdSymbol],
|
|
|
|
|
promise,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
const handled = emitUnhandledRejection(promise, promiseInfo);
|
|
|
|
|
if (!handled) emitUnhandledRejectionWarning(promiseInfo);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* --unhandled-rejections=none:
|
|
|
|
|
* Emit 'unhandledRejection', but do not emit any warning.
|
|
|
|
|
* @type {UnhandledRejectionsModeHandler}
|
|
|
|
|
*/
|
|
|
|
|
function ignoreUnhandledRejectionsMode(promise, promiseInfo) {
|
|
|
|
|
emitUnhandledRejection(promise, promiseInfo);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* --unhandled-rejections=warn:
|
|
|
|
|
* Emit 'unhandledRejection', then emit 'UnhandledPromiseRejectionWarning'.
|
|
|
|
|
* @type {UnhandledRejectionsModeHandler}
|
|
|
|
|
*/
|
|
|
|
|
function alwaysWarnUnhandledRejectionsMode(promise, promiseInfo) {
|
|
|
|
|
emitUnhandledRejection(promise, promiseInfo);
|
|
|
|
|
emitUnhandledRejectionWarning(promiseInfo);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* --unhandled-rejections=throw:
|
|
|
|
|
* Emit 'unhandledRejection', if it's unhandled, emit
|
|
|
|
|
* 'uncaughtException'. If it's not handled, print the error to stderr
|
|
|
|
|
* and exit the process.
|
|
|
|
|
* @type {UnhandledRejectionsModeHandler}
|
|
|
|
|
*/
|
|
|
|
|
function throwUnhandledRejectionsMode(promise, promiseInfo) {
|
|
|
|
|
const reason = promiseInfo.reason;
|
|
|
|
|
const handled = emitUnhandledRejection(promise, promiseInfo);
|
|
|
|
|
if (!handled) {
|
|
|
|
|
const err = isErrorLike(reason) ?
|
|
|
|
|
reason :
|
|
|
|
|
new UnhandledPromiseRejection(reason);
|
|
|
|
|
// This destroys the async stack, don't clear it after
|
|
|
|
|
triggerUncaughtException(err, true /* fromPromise */);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* --unhandled-rejections=warn-with-error-code:
|
|
|
|
|
* Emit 'unhandledRejection', if it's unhandled, emit
|
|
|
|
|
* 'UnhandledPromiseRejectionWarning', then set process exit code to 1.
|
|
|
|
|
* @type {UnhandledRejectionsModeHandler}
|
|
|
|
|
*/
|
|
|
|
|
function warnWithErrorCodeUnhandledRejectionsMode(promise, promiseInfo) {
|
|
|
|
|
const handled = emitUnhandledRejection(promise, promiseInfo);
|
|
|
|
|
if (!handled) {
|
|
|
|
|
emitUnhandledRejectionWarning(promiseInfo);
|
|
|
|
|
process.exitCode = kGenericUserError;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @returns {UnhandledRejectionsModeHandler}
|
|
|
|
|
*/
|
|
|
|
|
function getUnhandledRejectionsMode() {
|
|
|
|
|
const { getOptionValue } = require('internal/options');
|
|
|
|
|
switch (getOptionValue('--unhandled-rejections')) {
|
|
|
|
|
case 'none':
|
|
|
|
|
return ignoreUnhandledRejectionsMode;
|
|
|
|
|
case 'warn':
|
|
|
|
|
return alwaysWarnUnhandledRejectionsMode;
|
|
|
|
|
case 'strict':
|
|
|
|
|
return strictUnhandledRejectionsMode;
|
|
|
|
|
case 'throw':
|
|
|
|
|
return throwUnhandledRejectionsMode;
|
|
|
|
|
case 'warn-with-error-code':
|
|
|
|
|
return warnWithErrorCodeUnhandledRejectionsMode;
|
|
|
|
|
default:
|
|
|
|
|
return throwUnhandledRejectionsMode;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If this method returns true, we've executed user code or triggered
|
|
|
|
|
// a warning to be emitted which requires the microtask and next tick
|
|
|
|
|
// queues to be drained again.
|
|
|
|
|
function processPromiseRejections() {
|
|
|
|
|
let maybeScheduledTicksOrMicrotasks = asyncHandledRejections.length > 0;
|
|
|
|
|
|
|
|
|
|
while (asyncHandledRejections.length > 0) {
|
|
|
|
|
while (asyncHandledRejections.length !== 0) {
|
|
|
|
|
const { promise, warning } = ArrayPrototypeShift(asyncHandledRejections);
|
|
|
|
|
if (!process.emit('rejectionHandled', promise)) {
|
|
|
|
|
process.emitWarning(warning);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let len = pendingUnhandledRejections.length;
|
|
|
|
|
while (len--) {
|
|
|
|
|
const promise = ArrayPrototypeShift(pendingUnhandledRejections);
|
|
|
|
|
const promiseInfo = maybeUnhandledPromises.get(promise);
|
|
|
|
|
if (promiseInfo === undefined) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
promiseInfo.warned = true;
|
|
|
|
|
const { reason, uid, emit } = promiseInfo;
|
|
|
|
|
let needPop = true;
|
|
|
|
|
let promiseAsyncId;
|
|
|
|
|
|
|
|
|
|
const pending = pendingUnhandledRejections;
|
|
|
|
|
pendingUnhandledRejections = new SafeMap();
|
|
|
|
|
|
|
|
|
|
for (const { 0: promise, 1: promiseInfo } of pending.entries()) {
|
|
|
|
|
maybeUnhandledPromises.set(promise, promiseInfo);
|
|
|
|
|
|
|
|
|
|
promiseInfo.warned = true;
|
|
|
|
|
|
|
|
|
|
let needPop = true;
|
|
|
|
|
const {
|
|
|
|
|
[kAsyncIdSymbol]: promiseAsyncId,
|
|
|
|
|
[kTriggerAsyncIdSymbol]: promiseTriggerAsyncId,
|
|
|
|
|
} = promise;
|
|
|
|
|
// We need to check if async_hooks are enabled
|
|
|
|
|
// don't use enabledHooksExist as a Promise could
|
|
|
|
|
// come from a vm.* context and not have an async id
|
|
|
|
|
if (typeof promiseAsyncId !== 'undefined') {
|
|
|
|
|
promiseAsyncId = promise[kAsyncIdSymbol];
|
|
|
|
|
if (promiseAsyncId !== undefined) {
|
|
|
|
|
pushAsyncContext(
|
|
|
|
|
promiseAsyncId,
|
|
|
|
|
promiseTriggerAsyncId,
|
|
|
|
|
promise[kTriggerAsyncIdSymbol],
|
|
|
|
|
promise,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
switch (unhandledRejectionsMode) {
|
|
|
|
|
case kStrictUnhandledRejections: {
|
|
|
|
|
const err = isErrorLike(reason) ?
|
|
|
|
|
reason : generateUnhandledRejectionError(reason);
|
|
|
|
|
// This destroys the async stack, don't clear it after
|
|
|
|
|
triggerUncaughtException(err, true /* fromPromise */);
|
|
|
|
|
if (typeof promiseAsyncId !== 'undefined') {
|
|
|
|
|
pushAsyncContext(
|
|
|
|
|
promise[kAsyncIdSymbol],
|
|
|
|
|
promise[kTriggerAsyncIdSymbol],
|
|
|
|
|
promise,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
const handled = emit(reason, promise, promiseInfo);
|
|
|
|
|
if (!handled) emitUnhandledRejectionWarning(uid, reason);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case kIgnoreUnhandledRejections: {
|
|
|
|
|
emit(reason, promise, promiseInfo);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case kAlwaysWarnUnhandledRejections: {
|
|
|
|
|
emit(reason, promise, promiseInfo);
|
|
|
|
|
emitUnhandledRejectionWarning(uid, reason);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case kThrowUnhandledRejections: {
|
|
|
|
|
const handled = emit(reason, promise, promiseInfo);
|
|
|
|
|
if (!handled) {
|
|
|
|
|
const err = isErrorLike(reason) ?
|
|
|
|
|
reason : generateUnhandledRejectionError(reason);
|
|
|
|
|
// This destroys the async stack, don't clear it after
|
|
|
|
|
triggerUncaughtException(err, true /* fromPromise */);
|
|
|
|
|
needPop = false;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case kWarnWithErrorCodeUnhandledRejections: {
|
|
|
|
|
const handled = emit(reason, promise, promiseInfo);
|
|
|
|
|
if (!handled) {
|
|
|
|
|
emitUnhandledRejectionWarning(uid, reason);
|
|
|
|
|
process.exitCode = kGenericUserError;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
needPop = unhandledRejectionsMode(promise, promiseInfo, promiseAsyncId);
|
|
|
|
|
} finally {
|
|
|
|
|
if (needPop) {
|
|
|
|
|
if (typeof promiseAsyncId !== 'undefined') {
|
|
|
|
|
popAsyncContext(promiseAsyncId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
needPop &&
|
|
|
|
|
promiseAsyncId !== undefined &&
|
|
|
|
|
popAsyncContext(promiseAsyncId);
|
|
|
|
|
}
|
|
|
|
|
maybeScheduledTicksOrMicrotasks = true;
|
|
|
|
|
}
|
|
|
|
|
return maybeScheduledTicksOrMicrotasks ||
|
|
|
|
|
pendingUnhandledRejections.length !== 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getErrorWithoutStack(name, message) {
|
|
|
|
|
// Reset the stack to prevent any overhead.
|
|
|
|
|
const tmp = Error.stackTraceLimit;
|
|
|
|
|
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0;
|
|
|
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
|
|
|
const err = new Error(message);
|
|
|
|
|
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = tmp;
|
|
|
|
|
ObjectDefineProperty(err, 'name', {
|
|
|
|
|
__proto__: null,
|
|
|
|
|
value: name,
|
|
|
|
|
enumerable: false,
|
|
|
|
|
writable: true,
|
|
|
|
|
configurable: true,
|
|
|
|
|
});
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function generateUnhandledRejectionError(reason) {
|
|
|
|
|
const message =
|
|
|
|
|
'This error originated either by ' +
|
|
|
|
|
'throwing inside of an async function without a catch block, ' +
|
|
|
|
|
'or by rejecting a promise which was not handled with .catch().' +
|
|
|
|
|
' The promise rejected with the reason ' +
|
|
|
|
|
`"${noSideEffectsToString(reason)}".`;
|
|
|
|
|
|
|
|
|
|
const err = getErrorWithoutStack('UnhandledPromiseRejection', message);
|
|
|
|
|
err.code = 'ERR_UNHANDLED_REJECTION';
|
|
|
|
|
return err;
|
|
|
|
|
pendingUnhandledRejections.size !== 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function listenForRejections() {
|
|
|
|
|
setPromiseRejectCallback(promiseRejectHandler);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
hasRejectionToWarn,
|
|
|
|
|
setHasRejectionToWarn,
|
|
|
|
|
|