diff --git a/doc/api/deprecations.md b/doc/api/deprecations.md
index 79a8f896f1533e..c6801645f952ee 100644
--- a/doc/api/deprecations.md
+++ b/doc/api/deprecations.md
@@ -193,11 +193,11 @@ See [`Intl.Segmenter`](https://github.com/tc39/proposal-intl-segmenter).
### DEP0018: Unhandled promise rejections
-Type: Runtime
+Type: End-of-Life
-Unhandled promise rejections are deprecated. In the future, promise rejections
-that are not handled will terminate the Node.js process with a non-zero exit
-code.
+Any unhandled promise rejection that is garbage collected is going to exit the
+process similar to unhandled exceptions. Please make sure to always handle all
+possible rejections.
### DEP0019: require('.') resolved outside directory
diff --git a/lib/internal/bootstrap_node.js b/lib/internal/bootstrap_node.js
index 3ce558611e46e1..21ddb09b52674e 100644
--- a/lib/internal/bootstrap_node.js
+++ b/lib/internal/bootstrap_node.js
@@ -359,7 +359,7 @@
const { clearIdStack, asyncIdStackSize } = async_wrap;
const { kAfter, kCurrentAsyncId, kInitTriggerId } = async_wrap.constants;
- process._fatalException = function(er) {
+ process._fatalException = function(er, fromPromise) {
var caught;
// It's possible that kInitTriggerId was set for a constructor call that
@@ -369,7 +369,7 @@
if (process.domain && process.domain._errorHandler)
caught = process.domain._errorHandler(er);
- if (!caught)
+ if (!caught && !fromPromise)
caught = process.emit('uncaughtException', er);
// If someone handled it, then great. otherwise, die in C++ land
diff --git a/lib/internal/process/promises.js b/lib/internal/process/promises.js
index 663e6f5fec6985..204057720f97c7 100644
--- a/lib/internal/process/promises.js
+++ b/lib/internal/process/promises.js
@@ -2,21 +2,13 @@
const { safeToString } = process.binding('util');
-const promiseRejectEvent = process._promiseRejectEvent;
-const hasBeenNotifiedProperty = new WeakMap();
-const promiseToGuidProperty = new WeakMap();
-const pendingUnhandledRejections = [];
-let lastPromiseId = 1;
-
-exports.setup = setupPromises;
-
-function getAsynchronousRejectionWarningObject(uid) {
- return new Error('Promise rejection was handled ' +
- `asynchronously (rejection id: ${uid})`);
-}
-
-function setupPromises(scheduleMicrotasks) {
- let deprecationWarned = false;
+exports.setup = function setup(scheduleMicrotasks) {
+ const promiseRejectEvent = process._promiseRejectEvent;
+ const hasBeenNotifiedProperty = new Map();
+ const promiseToGuidProperty = new Map();
+ const pendingUnhandledRejections = [];
+ const promiseInternals = {};
+ let lastPromiseId = 1;
process._setupPromises(function(event, promise, reason) {
if (event === promiseRejectEvent.unhandled)
@@ -25,7 +17,7 @@ function setupPromises(scheduleMicrotasks) {
rejectionHandled(promise);
else
require('assert').fail(null, null, 'unexpected PromiseRejectEvent');
- });
+ }, promiseInternals);
function unhandledRejection(promise, reason) {
hasBeenNotifiedProperty.set(promise, false);
@@ -35,34 +27,36 @@ function setupPromises(scheduleMicrotasks) {
function rejectionHandled(promise) {
const hasBeenNotified = hasBeenNotifiedProperty.get(promise);
- if (hasBeenNotified !== undefined) {
- hasBeenNotifiedProperty.delete(promise);
+ hasBeenNotifiedProperty.delete(promise);
+ if (hasBeenNotified) {
const uid = promiseToGuidProperty.get(promise);
promiseToGuidProperty.delete(promise);
- if (hasBeenNotified === true) {
- let warning = null;
- if (!process.listenerCount('rejectionHandled')) {
- // Generate the warning object early to get a good stack trace.
- warning = getAsynchronousRejectionWarningObject(uid);
- }
- process.nextTick(function() {
- if (!process.emit('rejectionHandled', promise)) {
- if (warning === null)
- warning = getAsynchronousRejectionWarningObject(uid);
- warning.name = 'PromiseRejectionHandledWarning';
- warning.id = uid;
- process.emitWarning(warning);
- }
- });
+ let warning = null;
+ if (!process.listenerCount('rejectionHandled')) {
+ // Generate the warning object early to get a good stack trace.
+ warning = new Error('Promise rejection was handled ' +
+ `asynchronously (rejection id: ${uid})`);
}
-
+ promiseInternals.untrackPromise(promise);
+ process.nextTick(function() {
+ if (!process.emit('rejectionHandled', promise)) {
+ if (warning === null)
+ warning = new Error('Promise rejection was handled ' +
+ `asynchronously (rejection id: ${uid})`);
+ warning.name = 'PromiseRejectionHandledWarning';
+ warning.id = uid;
+ process.emitWarning(warning);
+ }
+ });
+ } else {
+ promiseToGuidProperty.delete(promise);
}
}
- function emitWarning(uid, reason) {
- const warning = new Error(
- `Unhandled promise rejection (rejection id: ${uid}): ` +
- safeToString(reason));
+ function emitWarning(promise, reason) {
+ const uid = promiseToGuidProperty.get(promise);
+ const warning = new Error('Unhandled promise rejection ' +
+ `(rejection id: ${uid}): ${safeToString(reason)}`);
warning.name = 'UnhandledPromiseRejectionWarning';
warning.id = uid;
try {
@@ -73,14 +67,6 @@ function setupPromises(scheduleMicrotasks) {
// ignored
}
process.emitWarning(warning);
- if (!deprecationWarned) {
- deprecationWarned = true;
- process.emitWarning(
- 'Unhandled promise rejections are deprecated. In the future, ' +
- 'promise rejections that are not handled will terminate the ' +
- 'Node.js process with a non-zero exit code.',
- 'DeprecationWarning', 'DEP0018');
- }
}
function emitPendingUnhandledRejections() {
@@ -90,9 +76,9 @@ function setupPromises(scheduleMicrotasks) {
const reason = pendingUnhandledRejections.shift();
if (hasBeenNotifiedProperty.get(promise) === false) {
hasBeenNotifiedProperty.set(promise, true);
- const uid = promiseToGuidProperty.get(promise);
if (!process.emit('unhandledRejection', reason, promise)) {
- emitWarning(uid, reason);
+ promiseInternals.trackPromise(promise);
+ emitWarning(promise, reason);
} else {
hadListeners = true;
}
@@ -107,4 +93,4 @@ function setupPromises(scheduleMicrotasks) {
}
return emitPendingUnhandledRejections;
-}
+};
diff --git a/node.gyp b/node.gyp
index 338b7f324e4f6a..37f30a0b976203 100644
--- a/node.gyp
+++ b/node.gyp
@@ -228,6 +228,7 @@
'src/stream_wrap.cc',
'src/tcp_wrap.cc',
'src/timer_wrap.cc',
+ 'src/track-promise.cc',
'src/tracing/agent.cc',
'src/tracing/node_trace_buffer.cc',
'src/tracing/node_trace_writer.cc',
@@ -270,6 +271,8 @@
'src/node_revert.h',
'src/node_i18n.h',
'src/pipe_wrap.h',
+ 'src/track-promise.h',
+ 'src/track-promise-inl.h',
'src/tty_wrap.h',
'src/tcp_wrap.h',
'src/udp_wrap.h',
@@ -692,6 +695,7 @@
'<(OBJ_TRACING_PATH)<(OBJ_SEPARATOR)node_trace_buffer.<(OBJ_SUFFIX)',
'<(OBJ_TRACING_PATH)<(OBJ_SEPARATOR)node_trace_writer.<(OBJ_SUFFIX)',
'<(OBJ_TRACING_PATH)<(OBJ_SEPARATOR)trace_event.<(OBJ_SUFFIX)',
+ '<(OBJ_PATH)<(OBJ_SEPARATOR)track-promise.<(OBJ_SUFFIX)',
],
}],
['v8_enable_inspector==1', {
diff --git a/src/env-inl.h b/src/env-inl.h
index 28512003f0fe91..af8a7c0d29369d 100644
--- a/src/env-inl.h
+++ b/src/env-inl.h
@@ -280,7 +280,8 @@ inline Environment* Environment::GetCurrent(
inline Environment::Environment(IsolateData* isolate_data,
v8::Local context)
- : isolate_(context->GetIsolate()),
+ : promise_tracker_(this),
+ isolate_(context->GetIsolate()),
isolate_data_(isolate_data),
async_hooks_(context->GetIsolate()),
timer_base_(uv_now(isolate_data->event_loop())),
diff --git a/src/env.h b/src/env.h
index 25db2e14e9f346..dd2fd1cc65614b 100644
--- a/src/env.h
+++ b/src/env.h
@@ -31,6 +31,7 @@
#endif
#include "handle_wrap.h"
#include "req-wrap.h"
+#include "track-promise.h"
#include "util.h"
#include "uv.h"
#include "v8.h"
@@ -236,6 +237,7 @@ class ModuleWrap;
V(preference_string, "preference") \
V(priority_string, "priority") \
V(produce_cached_data_string, "produceCachedData") \
+ V(promise_rejection_index_string, "_promiseRejectionIndex") \
V(raw_string, "raw") \
V(read_host_object_string, "_readHostObject") \
V(readable_string, "readable") \
@@ -294,6 +296,7 @@ class ModuleWrap;
V(zero_return_string, "ZERO_RETURN")
#define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) \
+ V(array_from, v8::Function) \
V(as_external, v8::External) \
V(async_hooks_destroy_function, v8::Function) \
V(async_hooks_init_function, v8::Function) \
@@ -312,8 +315,9 @@ class ModuleWrap;
V(performance_entry_callback, v8::Function) \
V(performance_entry_template, v8::Function) \
V(process_object, v8::Object) \
- V(promise_reject_function, v8::Function) \
V(promise_wrap_template, v8::ObjectTemplate) \
+ V(promise_unhandled_rejection_function, v8::Function) \
+ V(promise_unhandled_rejection, v8::Function) \
V(push_values_to_array_function, v8::Function) \
V(randombytes_constructor_template, v8::ObjectTemplate) \
V(script_context_constructor_template, v8::FunctionTemplate) \
@@ -678,6 +682,9 @@ class Environment {
bool RemovePromiseHook(promise_hook_func fn, void* arg);
bool EmitNapiWarning();
+ PromiseTracker promise_tracker_;
+ int64_t promise_tracker_index_ = 0;
+
private:
inline void ThrowError(v8::Local (*fun)(v8::Local),
const char* errmsg);
diff --git a/src/node.cc b/src/node.cc
index 541c5f5eec641f..538b64144c482f 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -65,6 +65,8 @@
#include "req-wrap-inl.h"
#include "string_bytes.h"
#include "tracing/agent.h"
+#include "track-promise.h"
+#include "track-promise-inl.h"
#include "util.h"
#include "uv.h"
#if NODE_USE_V8_PLATFORM
@@ -90,6 +92,7 @@
#include
#include
+#include
#if defined(NODE_HAVE_I18N_SUPPORT)
#include
@@ -133,6 +136,7 @@ using v8::Array;
using v8::ArrayBuffer;
using v8::Boolean;
using v8::Context;
+using v8::Debug;
using v8::EscapableHandleScope;
using v8::Exception;
using v8::Float64Array;
@@ -158,6 +162,7 @@ using v8::PromiseRejectMessage;
using v8::PropertyCallbackInfo;
using v8::ScriptOrigin;
using v8::SealHandleScope;
+using v8::Set;
using v8::String;
using v8::TryCatch;
using v8::Uint32Array;
@@ -1299,6 +1304,49 @@ void SetupNextTick(const FunctionCallbackInfo& args) {
args.GetReturnValue().Set(Uint32Array::New(array_buffer, 0, fields_count));
}
+} // anonymous namespace
+
+
+Local GetPromiseReason(Environment* env, Local promise) {
+ Local internal_props =
+ Debug::GetInternalProperties(env->isolate(),
+ promise).ToLocalChecked().As();
+
+ return internal_props->Get(3);
+}
+
+
+void TrackPromise(const FunctionCallbackInfo& args) {
+ Environment* env = Environment::GetCurrent(args);
+
+ CHECK(args[0]->IsObject());
+ Local