summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/cli.md19
-rw-r--r--doc/api/process.md37
-rw-r--r--doc/node.13
-rw-r--r--lib/internal/main/worker_thread.js4
-rw-r--r--lib/internal/modules/cjs/loader.js5
-rw-r--r--lib/internal/process/execution.js5
-rw-r--r--lib/internal/process/promises.js92
-rw-r--r--lib/internal/process/task_queues.js6
-rw-r--r--src/node_errors.cc15
-rw-r--r--src/node_errors.h5
-rw-r--r--src/node_options.cc18
-rw-r--r--src/node_options.h1
-rw-r--r--src/node_task_queue.cc3
-rw-r--r--test/message/promise_always_throw_unhandled.js16
-rw-r--r--test/message/promise_always_throw_unhandled.out13
-rw-r--r--test/parallel/test-cli-node-options.js1
-rw-r--r--test/parallel/test-next-tick-errors.js4
-rw-r--r--test/parallel/test-promise-unhandled-error.js54
-rw-r--r--test/parallel/test-promise-unhandled-flag.js16
-rw-r--r--test/parallel/test-promise-unhandled-silent-no-hook.js22
-rw-r--r--test/parallel/test-promise-unhandled-silent.js23
-rw-r--r--test/parallel/test-promise-unhandled-warn-no-hook.js23
-rw-r--r--test/parallel/test-promise-unhandled-warn.js28
-rw-r--r--test/parallel/test-timers-immediate-queue-throw.js7
-rw-r--r--test/parallel/test-timers-unref-throw-then-ref.js6
25 files changed, 376 insertions, 50 deletions
diff --git a/doc/api/cli.md b/doc/api/cli.md
index 60d75a6c04..f56eb981a9 100644
--- a/doc/api/cli.md
+++ b/doc/api/cli.md
@@ -577,6 +577,23 @@ added: v2.4.0
Track heap object allocations for heap snapshots.
+### `--unhandled-rejections=mode`
+<!-- YAML
+added: REPLACEME
+-->
+
+By default all unhandled rejections trigger a warning plus a deprecation warning
+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:
+
+* `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.
+* `none`: Silence all warnings.
+
### `--use-bundled-ca`, `--use-openssl-ca`
<!-- YAML
added: v6.11.0
@@ -798,6 +815,7 @@ Node.js options that are allowed are:
- `--trace-sync-io`
- `--trace-warnings`
- `--track-heap-objects`
+- `--unhandled-rejections`
- `--use-bundled-ca`
- `--use-openssl-ca`
- `--v8-pool-size`
@@ -966,6 +984,7 @@ greater than `4` (its current default value). For more information, see the
[`process.setUncaughtExceptionCaptureCallback()`]: process.html#process_process_setuncaughtexceptioncapturecallback_fn
[`tls.DEFAULT_MAX_VERSION`]: tls.html#tls_tls_default_max_version
[`tls.DEFAULT_MIN_VERSION`]: tls.html#tls_tls_default_min_version
+[`unhandledRejection`]: process.html#process_event_unhandledrejection
[Chrome DevTools Protocol]: https://chromedevtools.github.io/devtools-protocol/
[REPL]: repl.html
[ScriptCoverage]: https://chromedevtools.github.io/devtools-protocol/tot/Profiler#type-ScriptCoverage
diff --git a/doc/api/process.md b/doc/api/process.md
index 5eabb6fad1..95f57f6932 100644
--- a/doc/api/process.md
+++ b/doc/api/process.md
@@ -205,8 +205,17 @@ most convenient for scripts).
### Event: 'uncaughtException'
<!-- YAML
added: v0.1.18
+changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs/node/pull/26599
+ description: Added the `origin` argument.
-->
+* `err` {Error} The uncaught exception.
+* `origin` {string} Indicates if the exception originates from an unhandled
+ rejection or from synchronous errors. Can either be `'uncaughtException'` or
+ `'unhandledRejection'`.
+
The `'uncaughtException'` event is emitted when an uncaught JavaScript
exception bubbles all the way back to the event loop. By default, Node.js
handles such exceptions by printing the stack trace to `stderr` and exiting
@@ -217,12 +226,13 @@ behavior. Alternatively, change the [`process.exitCode`][] in the
provided exit code. Otherwise, in the presence of such handler the process will
exit with 0.
-The listener function is called with the `Error` object passed as the only
-argument.
-
```js
-process.on('uncaughtException', (err) => {
- fs.writeSync(1, `Caught exception: ${err}\n`);
+process.on('uncaughtException', (err, origin) => {
+ fs.writeSync(
+ process.stderr.fd,
+ `Caught exception: ${err}\n` +
+ `Exception origin: ${origin}`
+ );
});
setTimeout(() => {
@@ -274,6 +284,10 @@ changes:
a process warning.
-->
+* `reason` {Error|any} The object with which the promise was rejected
+ (typically an [`Error`][] object).
+* `promise` {Promise} The rejected promise.
+
The `'unhandledRejection'` event is emitted whenever a `Promise` is rejected and
no error handler is attached to the promise within a turn of the event loop.
When programming with Promises, exceptions are encapsulated as "rejected
@@ -282,15 +296,9 @@ are propagated through a `Promise` chain. The `'unhandledRejection'` event is
useful for detecting and keeping track of promises that were rejected whose
rejections have not yet been handled.
-The listener function is called with the following arguments:
-
-* `reason` {Error|any} The object with which the promise was rejected
- (typically an [`Error`][] object).
-* `p` the `Promise` that was rejected.
-
```js
-process.on('unhandledRejection', (reason, p) => {
- console.log('Unhandled Rejection at:', p, 'reason:', reason);
+process.on('unhandledRejection', (reason, promise) => {
+ console.log('Unhandled Rejection at:', promise, 'reason:', reason);
// Application specific logging, throwing an error, or other logic here
});
@@ -317,7 +325,7 @@ as would typically be the case for other `'unhandledRejection'` events. To
address such failures, a non-operational
[`.catch(() => { })`][`promise.catch()`] handler may be attached to
`resource.loaded`, which would prevent the `'unhandledRejection'` event from
-being emitted. Alternatively, the [`'rejectionHandled'`][] event may be used.
+being emitted.
### Event: 'warning'
<!-- YAML
@@ -2282,7 +2290,6 @@ cases:
[`'exit'`]: #process_event_exit
[`'message'`]: child_process.html#child_process_event_message
-[`'rejectionHandled'`]: #process_event_rejectionhandled
[`'uncaughtException'`]: #process_event_uncaughtexception
[`ChildProcess.disconnect()`]: child_process.html#child_process_subprocess_disconnect
[`ChildProcess.send()`]: child_process.html#child_process_subprocess_send_message_sendhandle_options_callback
diff --git a/doc/node.1 b/doc/node.1
index 0fccabc4bc..e2b52b9e87 100644
--- a/doc/node.1
+++ b/doc/node.1
@@ -292,6 +292,9 @@ Print stack traces for process warnings (including deprecations).
.It Fl -track-heap-objects
Track heap object allocations for heap snapshots.
.
+.It Fl --unhandled-rejections=mode
+Define the behavior for unhandled rejections. Can be one of `strict` (raise an error), `warn` (enforce warnings) or `none` (silence warnings).
+.
.It Fl -use-bundled-ca , Fl -use-openssl-ca
Use bundled Mozilla CA store as supplied by current Node.js version or use OpenSSL's default CA store.
The default store is selectable at build-time.
diff --git a/lib/internal/main/worker_thread.js b/lib/internal/main/worker_thread.js
index 0e463dc935..f290a4acb2 100644
--- a/lib/internal/main/worker_thread.js
+++ b/lib/internal/main/worker_thread.js
@@ -137,11 +137,11 @@ port.on('message', (message) => {
});
// Overwrite fatalException
-process._fatalException = (error) => {
+process._fatalException = (error, fromPromise) => {
debug(`[${threadId}] gets fatal exception`);
let caught = false;
try {
- caught = originalFatalException.call(this, error);
+ caught = originalFatalException.call(this, error, fromPromise);
} catch (e) {
error = e;
}
diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js
index 5d0e777774..6d14c56db5 100644
--- a/lib/internal/modules/cjs/loader.js
+++ b/lib/internal/modules/cjs/loader.js
@@ -821,7 +821,10 @@ Module.runMain = function() {
return loader.import(pathToFileURL(process.argv[1]).pathname);
})
.catch((e) => {
- internalBinding('task_queue').triggerFatalException(e);
+ internalBinding('task_queue').triggerFatalException(
+ e,
+ true /* fromPromise */
+ );
});
// Handle any nextTicks added in the first tick of the program
process._tickCallback();
diff --git a/lib/internal/process/execution.js b/lib/internal/process/execution.js
index 5712b80eaf..7b651ba5d1 100644
--- a/lib/internal/process/execution.js
+++ b/lib/internal/process/execution.js
@@ -117,7 +117,7 @@ function noop() {}
// before calling into process._fatalException, or this function should
// take extra care of the async hooks before it schedules a setImmediate.
function createFatalException() {
- return (er) => {
+ return (er, fromPromise) => {
// It's possible that defaultTriggerAsyncId was set for a constructor
// call that threw and was never cleared. So clear it now.
clearDefaultTriggerAsyncId();
@@ -138,9 +138,10 @@ function createFatalException() {
} catch {} // Ignore the exception. Diagnostic reporting is unavailable.
}
+ const type = fromPromise ? 'unhandledRejection' : 'uncaughtException';
if (exceptionHandlerState.captureFn !== null) {
exceptionHandlerState.captureFn(er);
- } else if (!process.emit('uncaughtException', er)) {
+ } else if (!process.emit('uncaughtException', er, type)) {
// If someone handled it, then great. otherwise, die in C++ land
// since that means that we'll exit the process, emit the 'exit' event.
try {
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);
}
diff --git a/lib/internal/process/task_queues.js b/lib/internal/process/task_queues.js
index bc65d25307..12e34b7ff7 100644
--- a/lib/internal/process/task_queues.js
+++ b/lib/internal/process/task_queues.js
@@ -155,11 +155,11 @@ function runMicrotask() {
try {
callback();
} catch (error) {
- // TODO(devsnek) remove this if
+ // TODO(devsnek): Remove this if
// https://bugs.chromium.org/p/v8/issues/detail?id=8326
// is resolved such that V8 triggers the fatal exception
- // handler for microtasks
- triggerFatalException(error);
+ // handler for microtasks.
+ triggerFatalException(error, false /* fromPromise */);
} finally {
this.emitDestroy();
}
diff --git a/src/node_errors.cc b/src/node_errors.cc
index 3c04152974..83dbfc611f 100644
--- a/src/node_errors.cc
+++ b/src/node_errors.cc
@@ -10,6 +10,7 @@
namespace node {
using errors::TryCatchScope;
+using v8::Boolean;
using v8::Context;
using v8::Exception;
using v8::Function;
@@ -771,7 +772,8 @@ void DecorateErrorStack(Environment* env,
void FatalException(Isolate* isolate,
Local<Value> error,
- Local<Message> message) {
+ Local<Message> message,
+ bool from_promise) {
CHECK(!error.IsEmpty());
HandleScope scope(isolate);
@@ -794,9 +796,12 @@ void FatalException(Isolate* isolate,
// Do not call FatalException when _fatalException handler throws
fatal_try_catch.SetVerbose(false);
+ Local<Value> argv[2] = { error,
+ Boolean::New(env->isolate(), from_promise) };
+
// This will return true if the JS layer handled it, false otherwise
MaybeLocal<Value> caught = fatal_exception_function.As<Function>()->Call(
- env->context(), process_object, 1, &error);
+ env->context(), process_object, arraysize(argv), argv);
if (fatal_try_catch.HasTerminated()) return;
@@ -821,4 +826,10 @@ void FatalException(Isolate* isolate,
}
}
+void FatalException(Isolate* isolate,
+ Local<Value> error,
+ Local<Message> message) {
+ FatalException(isolate, error, message, false /* from_promise */);
+}
+
} // namespace node
diff --git a/src/node_errors.h b/src/node_errors.h
index b04a347f1e..c27f4b36fa 100644
--- a/src/node_errors.h
+++ b/src/node_errors.h
@@ -39,6 +39,11 @@ void FatalException(v8::Isolate* isolate,
Local<Value> error,
Local<Message> message);
+void FatalException(v8::Isolate* isolate,
+ Local<Value> error,
+ Local<Message> message,
+ bool from_promise);
+
// Helpers to construct errors similar to the ones provided by
// lib/internal/errors.js.
// Example: with `V(ERR_INVALID_ARG_TYPE, TypeError)`, there will be
diff --git a/src/node_options.cc b/src/node_options.cc
index c307c66a62..cb490dad75 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -141,6 +141,13 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors) {
errors->push_back("invalid value for --http-parser");
}
+ if (!unhandled_rejections.empty() &&
+ unhandled_rejections != "strict" &&
+ unhandled_rejections != "warn" &&
+ unhandled_rejections != "none") {
+ errors->push_back("invalid value for --unhandled-rejections");
+ }
+
#if HAVE_INSPECTOR
debug_options_.CheckOptions(errors);
#endif // HAVE_INSPECTOR
@@ -287,6 +294,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"custom loader",
&EnvironmentOptions::userland_loader,
kAllowedInEnvironment);
+ AddOption("--entry-type",
+ "set module type name of the entry point",
+ &EnvironmentOptions::module_type,
+ kAllowedInEnvironment);
AddOption("--es-module-specifier-resolution",
"Select extension resolution algorithm for es modules; "
"either 'explicit' (default) or 'node'",
@@ -342,9 +353,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"show stack traces on process warnings",
&EnvironmentOptions::trace_warnings,
kAllowedInEnvironment);
- AddOption("--entry-type",
- "set module type name of the entry point",
- &EnvironmentOptions::module_type,
+ AddOption("--unhandled-rejections",
+ "define unhandled rejections behavior. Options are 'strict' (raise "
+ "an error), 'warn' (enforce warnings) or 'none' (silence warnings)",
+ &EnvironmentOptions::unhandled_rejections,
kAllowedInEnvironment);
AddOption("--check",
diff --git a/src/node_options.h b/src/node_options.h
index 59baea562a..0fb480b397 100644
--- a/src/node_options.h
+++ b/src/node_options.h
@@ -114,6 +114,7 @@ class EnvironmentOptions : public Options {
bool trace_deprecation = false;
bool trace_sync_io = false;
bool trace_warnings = false;
+ std::string unhandled_rejections;
std::string userland_loader;
bool syntax_check_only = false;
diff --git a/src/node_task_queue.cc b/src/node_task_queue.cc
index b832a66d8b..b125f5d01e 100644
--- a/src/node_task_queue.cc
+++ b/src/node_task_queue.cc
@@ -116,7 +116,8 @@ static void TriggerFatalException(const FunctionCallbackInfo<Value>& args) {
ReportException(env, exception, message);
Abort();
}
- FatalException(isolate, exception, message);
+ bool from_promise = args[1]->IsTrue();
+ FatalException(isolate, exception, message, from_promise);
}
static void Initialize(Local<Object> target,
diff --git a/test/message/promise_always_throw_unhandled.js b/test/message/promise_always_throw_unhandled.js
new file mode 100644
index 0000000000..d9128f34a5
--- /dev/null
+++ b/test/message/promise_always_throw_unhandled.js
@@ -0,0 +1,16 @@
+// Flags: --unhandled-rejections=strict
+'use strict';
+
+require('../common');
+
+// Check that the process will exit on the first unhandled rejection in case the
+// unhandled rejections mode is set to `'strict'`.
+
+const ref1 = new Promise(() => {
+ throw new Error('One');
+});
+
+const ref2 = Promise.reject(new Error('Two'));
+
+// Keep the event loop alive to actually detect the unhandled rejection.
+setTimeout(() => console.log(ref1, ref2), 1000);
diff --git a/test/message/promise_always_throw_unhandled.out b/test/message/promise_always_throw_unhandled.out
new file mode 100644
index 0000000000..8d03026856
--- /dev/null
+++ b/test/message/promise_always_throw_unhandled.out
@@ -0,0 +1,13 @@
+*promise_always_throw_unhandled.js:*
+ throw new Error('One');
+ ^
+Error: One
+ at *promise_always_throw_unhandled.js:*:*
+ at new Promise (<anonymous>)
+ at Object.<anonymous> (*promise_always_throw_unhandled.js:*:*)
+ at *
+ at *
+ at *
+ at *
+ at *
+ at *
diff --git a/test/parallel/test-cli-node-options.js b/test/parallel/test-cli-node-options.js
index 5fd6c1f2c9..d3d1938bf8 100644
--- a/test/parallel/test-cli-node-options.js
+++ b/test/parallel/test-cli-node-options.js
@@ -34,6 +34,7 @@ expect('--trace-event-file-pattern {pid}-${rotation}.trace_events', 'B\n');
// eslint-disable-next-line no-template-curly-in-string
expect('--trace-event-file-pattern {pid}-${rotation}.trace_events ' +
'--trace-event-categories node.async_hooks', 'B\n');
+expect('--unhandled-rejections=none', 'B\n');
if (!common.isWindows) {
expect('--perf-basic-prof', 'B\n');
diff --git a/test/parallel/test-next-tick-errors.js b/test/parallel/test-next-tick-errors.js
index 51ed2524a0..bf142bb351 100644
--- a/test/parallel/test-next-tick-errors.js
+++ b/test/parallel/test-next-tick-errors.js
@@ -61,7 +61,9 @@ testNextTickWith('str');
testNextTickWith({});
testNextTickWith([]);
-process.on('uncaughtException', function() {
+process.on('uncaughtException', function(err, errorOrigin) {
+ assert.strictEqual(errorOrigin, 'uncaughtException');
+
if (!exceptionHandled) {
exceptionHandled = true;
order.push('B');
diff --git a/test/parallel/test-promise-unhandled-error.js b/test/parallel/test-promise-unhandled-error.js
new file mode 100644
index 0000000000..bb906fdcbe
--- /dev/null
+++ b/test/parallel/test-promise-unhandled-error.js
@@ -0,0 +1,54 @@
+// Flags: --unhandled-rejections=strict
+'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'));
+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));
diff --git a/test/parallel/test-promise-unhandled-flag.js b/test/parallel/test-promise-unhandled-flag.js
new file mode 100644
index 0000000000..40a9030917
--- /dev/null
+++ b/test/parallel/test-promise-unhandled-flag.js
@@ -0,0 +1,16 @@
+'use strict';
+
+require('../common');
+const assert = require('assert');
+const cp = require('child_process');
+
+// Verify that a faulty environment variable throws on bootstrapping.
+// Therefore we do not need any special handling for the child process.
+const child = cp.spawnSync(
+ process.execPath,
+ ['--unhandled-rejections=foobar', __filename]
+);
+
+assert.strictEqual(child.stdout.toString(), '');
+assert(child.stderr.includes(
+ 'invalid value for --unhandled-rejections'), child.stderr);
diff --git a/test/parallel/test-promise-unhandled-silent-no-hook.js b/test/parallel/test-promise-unhandled-silent-no-hook.js
new file mode 100644
index 0000000000..db4f7a8471
--- /dev/null
+++ b/test/parallel/test-promise-unhandled-silent-no-hook.js
@@ -0,0 +1,22 @@
+// Flags: --unhandled-rejections=none
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+
+common.disableCrashOnUnhandledRejection();
+
+// Verify that ignoring unhandled rejection works fine and that no warning is
+// logged even though there is no unhandledRejection hook attached.
+
+new Promise(() => {
+ throw new Error('One');
+});
+
+Promise.reject('test');
+
+process.on('warning', common.mustNotCall('warning'));
+process.on('uncaughtException', common.mustNotCall('uncaughtException'));
+process.on('exit', assert.strictEqual.bind(null, 0));
+
+setTimeout(common.mustCall(), 2);
diff --git a/test/parallel/test-promise-unhandled-silent.js b/test/parallel/test-promise-unhandled-silent.js
new file mode 100644
index 0000000000..c9ccc0118e
--- /dev/null
+++ b/test/parallel/test-promise-unhandled-silent.js
@@ -0,0 +1,23 @@
+// Flags: --unhandled-rejections=none
+'use strict';
+
+const common = require('../common');
+
+common.disableCrashOnUnhandledRejection();
+
+// Verify that ignoring unhandled rejection works fine and that no warning is
+// logged.
+
+new Promise(() => {
+ throw new Error('One');
+});
+
+Promise.reject('test');
+
+process.on('warning', common.mustNotCall('warning'));
+process.on('uncaughtException', common.mustNotCall('uncaughtException'));
+process.on('rejectionHandled', common.mustNotCall('rejectionHandled'));
+
+process.on('unhandledRejection', common.mustCall(2));
+
+setTimeout(common.mustCall(), 2);
diff --git a/test/parallel/test-promise-unhandled-warn-no-hook.js b/test/parallel/test-promise-unhandled-warn-no-hook.js
new file mode 100644
index 0000000000..91784448ad
--- /dev/null
+++ b/test/parallel/test-promise-unhandled-warn-no-hook.js
@@ -0,0 +1,23 @@
+// Flags: --unhandled-rejections=warn
+'use strict';
+
+const common = require('../common');
+
+common.disableCrashOnUnhandledRejection();
+
+// Verify that ignoring unhandled rejection works fine and that no warning is
+// logged.
+
+new Promise(() => {
+ throw new Error('One');
+});
+
+Promise.reject('test');
+
+// Unhandled rejections trigger two warning per rejection. One is the rejection
+// reason and the other is a note where this warning is coming from.
+process.on('warning', common.mustCall(4));
+process.on('uncaughtException', common.mustNotCall('uncaughtException'));
+process.on('rejectionHandled', common.mustNotCall('rejectionHandled'));
+
+setTimeout(common.mustCall(), 2);
diff --git a/test/parallel/test-promise-unhandled-warn.js b/test/parallel/test-promise-unhandled-warn.js
new file mode 100644
index 0000000000..62ddc69d8c
--- /dev/null
+++ b/test/parallel/test-promise-unhandled-warn.js
@@ -0,0 +1,28 @@
+// Flags: --unhandled-rejections=warn
+'use strict';
+
+const common = require('../common');
+
+common.disableCrashOnUnhandledRejection();
+
+// Verify that ignoring unhandled rejection works fine and that no warning is
+// logged.
+
+new Promise(() => {
+ throw new Error('One');
+});
+
+Promise.reject('test');
+
+// Unhandled rejections trigger two warning per rejection. One is the rejection
+// reason and the other is a note where this warning is coming from.
+process.on('warning', common.mustCall(4));
+process.on('uncaughtException', common.mustNotCall('uncaughtException'));
+process.on('rejectionHandled', common.mustCall(2));
+
+process.on('unhandledRejection', (reason, promise) => {
+ // Handle promises but still warn!
+ promise.catch(() => {});
+});
+
+setTimeout(common.mustCall(), 2);
diff --git a/test/parallel/test-timers-immediate-queue-throw.js b/test/parallel/test-timers-immediate-queue-throw.js
index 9929b27ab2..477f0388f6 100644
--- a/test/parallel/test-timers-immediate-queue-throw.js
+++ b/test/parallel/test-timers-immediate-queue-throw.js
@@ -23,8 +23,11 @@ const errObj = {
message: 'setImmediate Err'
};
-process.once('uncaughtException', common.expectsError(errObj));
-process.once('uncaughtException', () => assert.strictEqual(stage, 0));
+process.once('uncaughtException', common.mustCall((err, errorOrigin) => {
+ assert.strictEqual(errorOrigin, 'uncaughtException');
+ assert.strictEqual(stage, 0);
+ common.expectsError(errObj)(err);
+}));
const d1 = domain.create();
d1.once('error', common.expectsError(errObj));
diff --git a/test/parallel/test-timers-unref-throw-then-ref.js b/test/parallel/test-timers-unref-throw-then-ref.js
index d3ae27e835..1dd5fdd0ad 100644
--- a/test/parallel/test-timers-unref-throw-then-ref.js
+++ b/test/parallel/test-timers-unref-throw-then-ref.js
@@ -2,8 +2,10 @@
const common = require('../common');
const assert = require('assert');
-process.once('uncaughtException', common.expectsError({
- message: 'Timeout Error'
+process.once('uncaughtException', common.mustCall((err) => {
+ common.expectsError({
+ message: 'Timeout Error'
+ })(err);
}));
let called = false;