mirror of
https://github.com/nodejs/node.git
synced 2026-10-14 10:26:26 -04:00
process: improve event-loop
PR-URL: https://github.com/nodejs/node/pull/52108 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
This commit is contained in:
1 parent
30a35ae522
commit
0bbc62c42a
6 files changed
+363
-194
No files matched your search
+328
-191
@@ -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;
|
||||
}
|
||||
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;
|
||||
const { reason, uid, emit } = promiseInfo;
|
||||
|
||||
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,
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
at *
|
||||
at *
|
||||
at *
|
||||
at *
|
||||
(node:*) Error: This was rejected
|
||||
at *
|
||||
at *
|
||||
|
||||
@@ -46,7 +46,9 @@ process.on('uncaughtException', common.mustCall((err, origin) => {
|
||||
counter.dec();
|
||||
assert.strictEqual(origin, 'unhandledRejection', err);
|
||||
const knownError = errors.shift();
|
||||
assert.deepStrictEqual(err, knownError);
|
||||
assert.strictEqual(err.name, knownError.name);
|
||||
assert.strictEqual(err.toString(), knownError.toString());
|
||||
assert.strictEqual(err.code, knownError.code);
|
||||
// Check if the errors are reference equal.
|
||||
assert(identical.shift() ? err === knownError : err !== knownError);
|
||||
}, 2));
|
||||
@@ -46,7 +46,8 @@ process.on('uncaughtException', common.mustCall((err, origin) => {
|
||||
counter.dec();
|
||||
assert.strictEqual(origin, 'unhandledRejection', err);
|
||||
const knownError = errors.shift();
|
||||
assert.deepStrictEqual(err, knownError);
|
||||
assert.strictEqual(err.message, knownError.message);
|
||||
assert.strictEqual(err.code, knownError.code);
|
||||
// Check if the errors are reference equal.
|
||||
assert(identical.shift() ? err === knownError : err !== knownError);
|
||||
}, 2));
|
||||
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
function delay(time) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, time);
|
||||
});
|
||||
}
|
||||
|
||||
async function test() {
|
||||
for (let i = 0; i < 100000; i++) {
|
||||
await new Promise((resolve, reject) => {
|
||||
reject('value');
|
||||
})
|
||||
.then(() => { }, () => { });
|
||||
}
|
||||
|
||||
const time0 = Date.now();
|
||||
await delay(0);
|
||||
|
||||
const diff = Date.now() - time0;
|
||||
assert.ok(Date.now() - time0 < 500, `Expected less than 500ms, got ${diff}ms`);
|
||||
}
|
||||
|
||||
test();
|
||||
@@ -47,7 +47,8 @@ process.on('uncaughtException', common.mustCall((err, origin) => {
|
||||
counter.dec();
|
||||
assert.strictEqual(origin, 'unhandledRejection', err);
|
||||
const knownError = errors.shift();
|
||||
assert.deepStrictEqual(err, knownError);
|
||||
assert.strictEqual(err.message, knownError.message);
|
||||
assert.strictEqual(err.code, knownError.code);
|
||||
// Check if the errors are reference equal.
|
||||
assert(identical.shift() ? err === knownError : err !== knownError);
|
||||
}, 2));
|
||||
Reference in new issue
Block a user