diff options
Diffstat (limited to 'lib/internal/process/promises.js')
-rw-r--r-- | lib/internal/process/promises.js | 92 |
1 files changed, 76 insertions, 16 deletions
diff --git a/lib/internal/process/promises.js b/lib/internal/process/promises.js index f3e118e2ce..95161ac7a8 100644 --- a/lib/internal/process/promises.js +++ b/lib/internal/process/promises.js @@ -1,6 +1,10 @@ 'use strict'; -const { safeToString } = internalBinding('util'); +const { Object } = primordials; + +const { + safeToString +} = internalBinding('util'); const { tickInfo, promiseRejectEvents: { @@ -9,7 +13,8 @@ const { kPromiseResolveAfterResolved, kPromiseRejectAfterResolved }, - setPromiseRejectCallback + setPromiseRejectCallback, + triggerFatalException } = internalBinding('task_queue'); // *Must* match Environment::TickInfo::Fields in src/env.h. @@ -20,6 +25,15 @@ const pendingUnhandledRejections = []; const asyncHandledRejections = []; let lastPromiseId = 0; +const states = { + none: 0, + warn: 1, + strict: 2, + default: 3 +}; + +let state; + function setHasRejectionToWarn(value) { tickInfo[kHasRejectionToWarn] = value ? 1 : 0; } @@ -29,6 +43,10 @@ function hasRejectionToWarn() { } function promiseRejectHandler(type, promise, reason) { + if (state === undefined) { + const { getOptionValue } = require('internal/options'); + state = states[getOptionValue('--unhandled-rejections') || 'default']; + } switch (type) { case kPromiseRejectWithNoHandler: unhandledRejection(promise, reason); @@ -59,6 +77,7 @@ function unhandledRejection(promise, reason) { uid: ++lastPromiseId, warned: false }); + // This causes the promise to be referenced at least for one tick. pendingUnhandledRejections.push(promise); setHasRejectionToWarn(true); } @@ -85,14 +104,16 @@ function handledRejection(promise) { const unhandledRejectionErrName = 'UnhandledPromiseRejectionWarning'; function emitWarning(uid, reason) { - // eslint-disable-next-line no-restricted-syntax - const warning = new Error( + if (state === states.none) { + return; + } + const warning = getError( + 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(). ' + - `(rejection id: ${uid})` + 'throwing inside of an async function without a catch block, ' + + 'or by rejecting a promise which was not handled with .catch(). ' + + `(rejection id: ${uid})` ); - warning.name = unhandledRejectionErrName; try { if (reason instanceof Error) { warning.stack = reason.stack; @@ -108,7 +129,7 @@ function emitWarning(uid, reason) { let deprecationWarned = false; function emitDeprecationWarning() { - if (!deprecationWarned) { + if (state === states.default && !deprecationWarned) { deprecationWarned = true; process.emitWarning( 'Unhandled promise rejections are deprecated. In the future, ' + @@ -133,18 +154,57 @@ function processPromiseRejections() { while (len--) { const promise = pendingUnhandledRejections.shift(); const promiseInfo = maybeUnhandledPromises.get(promise); - if (promiseInfo !== undefined) { - promiseInfo.warned = true; - const { reason, uid } = promiseInfo; - if (!process.emit('unhandledRejection', reason, promise)) { - emitWarning(uid, reason); - } - maybeScheduledTicks = true; + if (promiseInfo === undefined) { + continue; + } + promiseInfo.warned = true; + const { reason, uid } = promiseInfo; + if (state === states.strict) { + fatalException(reason); } + if (!process.emit('unhandledRejection', reason, promise) || + // Always warn in case the user requested it. + state === states.warn) { + emitWarning(uid, reason); + } + maybeScheduledTicks = true; } return maybeScheduledTicks || pendingUnhandledRejections.length !== 0; } +function getError(name, message) { + // Reset the stack to prevent any overhead. + const tmp = Error.stackTraceLimit; + Error.stackTraceLimit = 0; + // eslint-disable-next-line no-restricted-syntax + const err = new Error(message); + Error.stackTraceLimit = tmp; + Object.defineProperty(err, 'name', { + value: name, + enumerable: false, + writable: true, + configurable: true, + }); + return err; +} + +function fatalException(reason) { + let err; + if (reason instanceof Error) { + err = reason; + } else { + err = getError( + 'UnhandledPromiseRejection', + '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 "${safeToString(reason)}".` + ); + err.code = 'ERR_UNHANDLED_REJECTION'; + } + triggerFatalException(err, true /* fromPromise */); +} + function listenForRejections() { setPromiseRejectCallback(promiseRejectHandler); } |