mirror of
https://github.com/nodejs/node.git
synced 2024-11-29 07:00:59 +01:00
process: add unhandled-rejection throw and warn-with-error-code
This PR defines two new modes for the --unhandled-rejections flag. The first mode is called "throw". The "throw" mode first emits unhandledRejection. If this hook is not set, the "throw" mode will raise the unhandled rejection as an uncaught exception. The second mode is called "warn-with-error-code". The "warn-with-error-code" mode first emits unhandledRejection. If this hook is not set, the "warn-with-error-code" mode will trigger a warning and set the process's exit code to 1. The PR doesn't change the default behavior for unhandled rejections. That will come in a separate PR. Refs: https://github.com/nodejs/node/pull/33021 PR-URL: https://github.com/nodejs/node/pull/33475 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
parent
bc71278563
commit
ff74e35c0b
@ -983,11 +983,15 @@ for the very first unhandled rejection in case no [`unhandledRejection`][] hook
|
||||
is used.
|
||||
|
||||
Using this flag allows to change what should happen when an unhandled rejection
|
||||
occurs. One of three modes can be chosen:
|
||||
occurs. One of the following modes can be chosen:
|
||||
|
||||
* `throw`: Emit [`unhandledRejection`][]. If this hook is not set, raise the
|
||||
unhandled rejection as an uncaught exception.
|
||||
* `strict`: Raise the unhandled rejection as an uncaught exception.
|
||||
* `warn`: Always trigger a warning, no matter if the [`unhandledRejection`][]
|
||||
hook is set or not but do not print the deprecation warning.
|
||||
* `warn-with-error-code`: Emit [`unhandledRejection`][]. If this hook is not
|
||||
set, trigger a warning, and set the process exit code to 1.
|
||||
* `none`: Silence all warnings.
|
||||
|
||||
### `--use-bundled-ca`, `--use-openssl-ca`
|
||||
|
@ -30,22 +30,37 @@ const pendingUnhandledRejections = [];
|
||||
const asyncHandledRejections = [];
|
||||
let lastPromiseId = 0;
|
||||
|
||||
// --unhandled-rejection=none:
|
||||
// --unhandled-rejections=none:
|
||||
// Emit 'unhandledRejection', but do not emit any warning.
|
||||
const kIgnoreUnhandledRejections = 0;
|
||||
// --unhandled-rejection=warn:
|
||||
|
||||
// --unhandled-rejections=warn:
|
||||
// Emit 'unhandledRejection', then emit 'UnhandledPromiseRejectionWarning'.
|
||||
const kAlwaysWarnUnhandledRejections = 1;
|
||||
// --unhandled-rejection=strict:
|
||||
|
||||
// --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 kThrowUnhandledRejections = 2;
|
||||
// --unhandled-rejection is unset:
|
||||
// Emit 'unhandledRejection', if it's handled, emit
|
||||
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;
|
||||
|
||||
// --unhandled-rejections is unset:
|
||||
// Emit 'unhandledRejection', if it's unhandled, emit
|
||||
// 'UnhandledPromiseRejectionWarning', then emit deprecation warning.
|
||||
const kDefaultUnhandledRejections = 3;
|
||||
const kDefaultUnhandledRejections = 5;
|
||||
|
||||
let unhandledRejectionsMode;
|
||||
|
||||
@ -65,7 +80,11 @@ function getUnhandledRejectionsMode() {
|
||||
case 'warn':
|
||||
return kAlwaysWarnUnhandledRejections;
|
||||
case 'strict':
|
||||
return kStrictUnhandledRejections;
|
||||
case 'throw':
|
||||
return kThrowUnhandledRejections;
|
||||
case 'warn-with-error-code':
|
||||
return kWarnWithErrorCodeUnhandledRejections;
|
||||
default:
|
||||
return kDefaultUnhandledRejections;
|
||||
}
|
||||
@ -188,7 +207,7 @@ function processPromiseRejections() {
|
||||
promiseInfo.warned = true;
|
||||
const { reason, uid } = promiseInfo;
|
||||
switch (unhandledRejectionsMode) {
|
||||
case kThrowUnhandledRejections: {
|
||||
case kStrictUnhandledRejections: {
|
||||
const err = reason instanceof Error ?
|
||||
reason : generateUnhandledRejectionError(reason);
|
||||
triggerUncaughtException(err, true /* fromPromise */);
|
||||
@ -205,6 +224,23 @@ function processPromiseRejections() {
|
||||
emitUnhandledRejectionWarning(uid, reason);
|
||||
break;
|
||||
}
|
||||
case kThrowUnhandledRejections: {
|
||||
const handled = process.emit('unhandledRejection', reason, promise);
|
||||
if (!handled) {
|
||||
const err = reason instanceof Error ?
|
||||
reason : generateUnhandledRejectionError(reason);
|
||||
triggerUncaughtException(err, true /* fromPromise */);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kWarnWithErrorCodeUnhandledRejections: {
|
||||
const handled = process.emit('unhandledRejection', reason, promise);
|
||||
if (!handled) {
|
||||
emitUnhandledRejectionWarning(uid, reason);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kDefaultUnhandledRejections: {
|
||||
const handled = process.emit('unhandledRejection', reason, promise);
|
||||
if (!handled) {
|
||||
|
@ -118,6 +118,8 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors) {
|
||||
}
|
||||
|
||||
if (!unhandled_rejections.empty() &&
|
||||
unhandled_rejections != "warn-with-error-code" &&
|
||||
unhandled_rejections != "throw" &&
|
||||
unhandled_rejections != "strict" &&
|
||||
unhandled_rejections != "warn" &&
|
||||
unhandled_rejections != "none") {
|
||||
|
10
test/message/promise_unhandled_warn_with_error.js
Normal file
10
test/message/promise_unhandled_warn_with_error.js
Normal file
@ -0,0 +1,10 @@
|
||||
// Flags: --unhandled-rejections=warn-with-error-code
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
common.disableCrashOnUnhandledRejection();
|
||||
|
||||
Promise.reject(new Error('alas'));
|
||||
process.on('exit', assert.strictEqual.bind(null, 1));
|
10
test/message/promise_unhandled_warn_with_error.out
Normal file
10
test/message/promise_unhandled_warn_with_error.out
Normal file
@ -0,0 +1,10 @@
|
||||
*UnhandledPromiseRejectionWarning: Error: alas
|
||||
at *promise_unhandled_warn_with_error.js:*:*
|
||||
at *
|
||||
at *
|
||||
at *
|
||||
at *
|
||||
at *
|
||||
at *
|
||||
(Use `node --trace-warnings ...` to show where the warning was created)
|
||||
*UnhandledPromiseRejectionWarning: 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: 1)
|
38
test/parallel/test-promise-unhandled-throw-handler.js
Normal file
38
test/parallel/test-promise-unhandled-throw-handler.js
Normal file
@ -0,0 +1,38 @@
|
||||
// Flags: --unhandled-rejections=throw
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const Countdown = require('../common/countdown');
|
||||
const assert = require('assert');
|
||||
|
||||
common.disableCrashOnUnhandledRejection();
|
||||
|
||||
// Verify that the unhandledRejection handler prevents triggering
|
||||
// uncaught exceptions
|
||||
|
||||
const err1 = new Error('One');
|
||||
|
||||
const errors = [err1, null];
|
||||
|
||||
const ref = new Promise(() => {
|
||||
throw err1;
|
||||
});
|
||||
// Explicitly reject `null`.
|
||||
Promise.reject(null);
|
||||
|
||||
process.on('warning', common.mustNotCall('warning'));
|
||||
process.on('rejectionHandled', common.mustNotCall('rejectionHandled'));
|
||||
process.on('exit', assert.strictEqual.bind(null, 0));
|
||||
process.on('uncaughtException', common.mustNotCall('uncaughtException'));
|
||||
|
||||
const timer = setTimeout(() => console.log(ref), 1000);
|
||||
|
||||
const counter = new Countdown(2, () => {
|
||||
clearTimeout(timer);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', common.mustCall((err) => {
|
||||
counter.dec();
|
||||
const knownError = errors.shift();
|
||||
assert.deepStrictEqual(err, knownError);
|
||||
}, 2));
|
55
test/parallel/test-promise-unhandled-throw.js
Normal file
55
test/parallel/test-promise-unhandled-throw.js
Normal file
@ -0,0 +1,55 @@
|
||||
// Flags: --unhandled-rejections=throw
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const Countdown = require('../common/countdown');
|
||||
const assert = require('assert');
|
||||
|
||||
common.disableCrashOnUnhandledRejection();
|
||||
|
||||
// Verify that unhandled rejections always trigger uncaught exceptions instead
|
||||
// of triggering unhandled rejections.
|
||||
|
||||
const err1 = new Error('One');
|
||||
const err2 = new Error(
|
||||
'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 "null".'
|
||||
);
|
||||
err2.code = 'ERR_UNHANDLED_REJECTION';
|
||||
Object.defineProperty(err2, 'name', {
|
||||
value: 'UnhandledPromiseRejection',
|
||||
writable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
const errors = [err1, err2];
|
||||
const identical = [true, false];
|
||||
|
||||
const ref = new Promise(() => {
|
||||
throw err1;
|
||||
});
|
||||
// Explicitly reject `null`.
|
||||
Promise.reject(null);
|
||||
|
||||
process.on('warning', common.mustNotCall('warning'));
|
||||
// If we add an unhandledRejection handler, the exception won't be thrown
|
||||
// process.on('unhandledRejection', common.mustCall(2));
|
||||
process.on('rejectionHandled', common.mustNotCall('rejectionHandled'));
|
||||
process.on('exit', assert.strictEqual.bind(null, 0));
|
||||
|
||||
const timer = setTimeout(() => console.log(ref), 1000);
|
||||
|
||||
const counter = new Countdown(2, () => {
|
||||
clearTimeout(timer);
|
||||
});
|
||||
|
||||
process.on('uncaughtException', common.mustCall((err, origin) => {
|
||||
counter.dec();
|
||||
assert.strictEqual(origin, 'unhandledRejection', err);
|
||||
const knownError = errors.shift();
|
||||
assert.deepStrictEqual(err, knownError);
|
||||
// Check if the errors are reference equal.
|
||||
assert(identical.shift() ? err === knownError : err !== knownError);
|
||||
}, 2));
|
Loading…
Reference in New Issue
Block a user