Skip to content

Commit d5409f4

Browse files
committed
process: Don't warn/throw on unhandled rejections when hook is set
--unhandled-rejections has three explicit modes (strict, warn, none) plus one implicit "default" mode, which logs an additional deprecation warning (DEP0018). Prior to this commit, the default mode was subtly different from warn mode. If the unhandledRejections hook is set, default mode suppresses all warnings. In warn mode, unhandledRejections would always fire a warning, regardless of whether the hook was set. In addition, prior to this commit, strict mode would always throw an exception, regardless of whether the hook was set. In this commit, all modes honor the unhandledRejections hook. If the user has set the hook, then the user has taken full responsibility over the behavior of unhandled rejections. In that case, no additional warnings or thrown exceptions will be fired, even in warn mode or strict mode. This commit is a stepping stone towards resolving DEP0018. After this commit, any code that includes an unhandledRejection hook will behave unchanged when we change the default mode. Refs: nodejs#26599
1 parent 91ca221 commit d5409f4

File tree

7 files changed

+53
-45
lines changed

7 files changed

+53
-45
lines changed

doc/api/cli.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -890,12 +890,20 @@ for the very first unhandled rejection in case no [`unhandledRejection`][] hook
890890
is used.
891891

892892
Using this flag allows to change what should happen when an unhandled rejection
893-
occurs. One of three modes can be chosen:
893+
occurs and the [`unhandledRejection`][] hook is unset.
894894

895-
* `strict`: Raise the unhandled rejection as an uncaught exception.
896-
* `warn`: Always trigger a warning, no matter if the [`unhandledRejection`][]
897-
hook is set or not but do not print the deprecation warning.
898-
* `none`: Silence all warnings.
895+
One of three modes can be chosen:
896+
897+
* `strict`: Emit [`unhandledRejection`][]. If this hook is not set, raise the
898+
unhandled rejection as an uncaught exception.
899+
* `warn`: Emit [`unhandledRejection`][]. If this hook is not set, trigger a
900+
warning. This is the default.
901+
* `none`: Emit [`unhandledRejection`][] and do nothing further.
902+
903+
In all three modes, setting the [`unhandledRejection`][] hook will suppress
904+
the exception/warning. The hook implementation can raise an exception (as in
905+
`strict` mode), log a warning (as in `warn` mode), or do nothing (as in `none`
906+
mode).
899907

900908
### `--use-bundled-ca`, `--use-openssl-ca`
901909
<!-- YAML

lib/internal/process/promises.js

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,17 @@ const pendingUnhandledRejections = [];
3030
const asyncHandledRejections = [];
3131
let lastPromiseId = 0;
3232

33-
// --unhandled-rejection=none:
33+
// --unhandled-rejections=none:
3434
// Emit 'unhandledRejection', but do not emit any warning.
3535
const kIgnoreUnhandledRejections = 0;
36-
// --unhandled-rejection=warn:
37-
// Emit 'unhandledRejection', then emit 'UnhandledPromiseRejectionWarning'.
38-
const kAlwaysWarnUnhandledRejections = 1;
39-
// --unhandled-rejection=strict:
40-
// Emit 'uncaughtException'. If it's not handled, print the error to stderr
36+
// --unhandled-rejections=warn:
37+
// Emit 'unhandledRejection', if it's unhandled, emit
38+
// 'UnhandledPromiseRejectionWarning'.
39+
const kWarnUnhandledRejections = 1;
40+
// --unhandled-rejections=strict:
41+
// Emit 'unhandledRejection', if it's unhandled, emit
42+
// 'uncaughtException'. If it's not handled, print the error to stderr
4143
// and exit the process.
42-
// Otherwise, emit 'unhandledRejection'. If 'unhandledRejection' is not
43-
// handled, emit 'UnhandledPromiseRejectionWarning'.
4444
const kThrowUnhandledRejections = 2;
4545
// --unhandled-rejection is unset:
4646
// Emit 'unhandledRejection', if it's handled, emit
@@ -62,10 +62,10 @@ function getUnhandledRejectionsMode() {
6262
switch (getOptionValue('--unhandled-rejections')) {
6363
case 'none':
6464
return kIgnoreUnhandledRejections;
65-
case 'warn':
66-
return kAlwaysWarnUnhandledRejections;
6765
case 'strict':
6866
return kThrowUnhandledRejections;
67+
case 'warn':
68+
return kWarnUnhandledRejections;
6969
default:
7070
return kDefaultUnhandledRejections;
7171
}
@@ -138,7 +138,8 @@ function emitUnhandledRejectionWarning(uid, reason) {
138138
'Unhandled promise rejection. This error originated either by ' +
139139
'throwing inside of an async function without a catch block, ' +
140140
'or by rejecting a promise which was not handled with .catch(). ' +
141-
'To terminate the node process on unhandled promise ' +
141+
'To suppress this warning, use process.on("unhandledRejection"). ' +
142+
'Or, to terminate the node process on unhandled promise ' +
142143
'rejection, use the CLI flag `--unhandled-rejections=strict` (see ' +
143144
'https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). ' +
144145
`(rejection id: ${uid})`
@@ -187,34 +188,30 @@ function processPromiseRejections() {
187188
}
188189
promiseInfo.warned = true;
189190
const { reason, uid } = promiseInfo;
190-
switch (unhandledRejectionsMode) {
191-
case kThrowUnhandledRejections: {
192-
const err = reason instanceof Error ?
193-
reason : generateUnhandledRejectionError(reason);
194-
triggerUncaughtException(err, true /* fromPromise */);
195-
const handled = process.emit('unhandledRejection', reason, promise);
196-
if (!handled) emitUnhandledRejectionWarning(uid, reason);
197-
break;
198-
}
199-
case kIgnoreUnhandledRejections: {
200-
process.emit('unhandledRejection', reason, promise);
201-
break;
202-
}
203-
case kAlwaysWarnUnhandledRejections: {
204-
process.emit('unhandledRejection', reason, promise);
205-
emitUnhandledRejectionWarning(uid, reason);
206-
break;
207-
}
208-
case kDefaultUnhandledRejections: {
209-
const handled = process.emit('unhandledRejection', reason, promise);
210-
if (!handled) {
191+
const handled = process.emit('unhandledRejection', reason, promise);
192+
if (!handled) {
193+
switch (unhandledRejectionsMode) {
194+
case kThrowUnhandledRejections: {
195+
const err = reason instanceof Error ?
196+
reason : generateUnhandledRejectionError(reason);
197+
triggerUncaughtException(err, true /* fromPromise */);
198+
break;
199+
}
200+
case kIgnoreUnhandledRejections: {
201+
break;
202+
}
203+
case kWarnUnhandledRejections: {
204+
emitUnhandledRejectionWarning(uid, reason);
205+
break;
206+
}
207+
case kDefaultUnhandledRejections: {
211208
emitUnhandledRejectionWarning(uid, reason);
212209
if (!deprecationWarned) {
213210
emitDeprecationWarning();
214211
deprecationWarned = true;
215212
}
213+
break;
216214
}
217-
break;
218215
}
219216
}
220217
maybeScheduledTicksOrMicrotasks = true;

test/message/promise_always_throw_unhandled.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Flags: --unhandled-rejections=strict
22
'use strict';
33

4-
require('../common');
4+
const common = require('../common');
5+
6+
common.disableCrashOnUnhandledRejection();
57

68
// Check that the process will exit on the first unhandled rejection in case the
79
// unhandled rejections mode is set to `'strict'`.

test/parallel/test-promise-unhandled-error.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ const ref = new Promise(() => {
3434
Promise.reject(null);
3535

3636
process.on('warning', common.mustNotCall('warning'));
37-
process.on('unhandledRejection', common.mustCall(2));
3837
process.on('rejectionHandled', common.mustNotCall('rejectionHandled'));
3938
process.on('exit', assert.strictEqual.bind(null, 0));
4039

test/parallel/test-promise-unhandled-warn.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Promise.reject('test');
1616

1717
// Unhandled rejections trigger two warning per rejection. One is the rejection
1818
// reason and the other is a note where this warning is coming from.
19-
process.on('warning', common.mustCall(4));
19+
process.on('warning', common.mustNotCall());
2020
process.on('uncaughtException', common.mustNotCall('uncaughtException'));
2121
process.on('rejectionHandled', common.mustCall(2));
2222

test/parallel/test-promises-unhandled-proxy-rejections.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ const expectedPromiseWarning = ['Unhandled promise rejection. ' +
1212
'This error originated either by throwing ' +
1313
'inside of an async function without a catch ' +
1414
'block, or by rejecting a promise which was ' +
15-
'not handled with .catch(). To terminate the ' +
16-
'node process on unhandled promise rejection, ' +
15+
'not handled with .catch(). To suppress this warning, ' +
16+
'use process.on("unhandledRejection"). Or, to terminate ' +
17+
'the node process on unhandled promise rejection, ' +
1718
'use the CLI flag `--unhandled-rejections=strict` (see ' +
1819
'https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). ' +
1920
'(rejection id: 1)'];

test/parallel/test-promises-unhandled-symbol-rejections.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ const expectedPromiseWarning = ['Unhandled promise rejection. ' +
1313
'This error originated either by throwing ' +
1414
'inside of an async function without a catch ' +
1515
'block, or by rejecting a promise which was ' +
16-
'not handled with .catch(). To terminate the ' +
17-
'node process on unhandled promise rejection, ' +
16+
'not handled with .catch(). To suppress this warning, ' +
17+
'use process.on("unhandledRejection"). Or, to terminate ' +
18+
'the node process on unhandled promise rejection, ' +
1819
'use the CLI flag `--unhandled-rejections=strict` (see ' +
1920
'https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). ' +
2021
'(rejection id: 1)'];

0 commit comments

Comments
 (0)