Skip to content

Commit 3dec7bb

Browse files
committed
util: expose promise events
1 parent c7962dc commit 3dec7bb

File tree

10 files changed

+435
-37
lines changed

10 files changed

+435
-37
lines changed

doc/api/util.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1631,6 +1631,80 @@ const module = new WebAssembly.Module(wasmBuffer);
16311631
util.types.isWebAssemblyCompiledModule(module); // Returns true
16321632
```
16331633

1634+
### util.createPromiseHook(hooks)
1635+
<!-- YAML
1636+
added: REPLACEME
1637+
-->
1638+
1639+
> Stability: 1 - Experimental
1640+
1641+
* `hooks` {Object}
1642+
* `resolve` {Function} Called when a promise resolve function is called. The
1643+
promise is not fulfilled or rejected at this point.
1644+
* `rejectWithNoHandler` {Function} Called when a promise rejects without any
1645+
handler.
1646+
* `handlerAddedAfterReject` {Function} Called when a promise handler is added
1647+
to a promise that rejected without a handler.
1648+
* `rejectAfterResolved` {Function} Called when a promise reject function is
1649+
called after resolution.
1650+
* `resolveAfterResolved` {Function} Called when a promise resolve function is
1651+
called after resolution.
1652+
1653+
Returns: {Object}
1654+
* `enable` {Function} Enables the hook
1655+
* `disable` {Function} Disables the hook
1656+
1657+
Registers the promise hook `hook` to be called for certain promise debugging
1658+
events.
1659+
1660+
```js
1661+
util.createPromiseHook({
1662+
resolve(promise) {
1663+
console.log('Promise did resolve');
1664+
},
1665+
rejectWithNoHandler(promise, value) {
1666+
console.log('Promise did rejectWithNoHandler with', value);
1667+
},
1668+
handlerAddedAfterReject(promise) {
1669+
console.log('Promise did handlerAddedAfterReject');
1670+
},
1671+
}).enable();
1672+
1673+
const x = Promise.resolve('success')
1674+
.then(() => Promise.reject('error'));
1675+
1676+
setTimeout(() => {
1677+
x.catch(() => {});
1678+
}, 10);
1679+
1680+
// Promise did resolve
1681+
// Promise did resolve
1682+
// Promise did rejectWithNoHandler with 'error'
1683+
// Promise did resolve
1684+
// Promise did handlerAddedAfterReject
1685+
// Promise did resolve
1686+
// Promise did rejectWithNoHandler with 'error'
1687+
// Promise did resolve
1688+
// Promise did handlerAddedAfterReject
1689+
// Promise did resolve
1690+
```
1691+
1692+
```js
1693+
util.createPromiseHook({
1694+
rejectAfterResolved(promise, value) {
1695+
console.log('Promise did rejectAfterResolved with', value);
1696+
},
1697+
}).enable();
1698+
1699+
new Promise((resolve, reject) => {
1700+
resolve('success');
1701+
1702+
reject('oops');
1703+
});
1704+
1705+
// Promise did rejectAfterResolved with 'oops'
1706+
```
1707+
16341708
## Deprecated APIs
16351709

16361710
The following APIs are deprecated and should no longer be used. Existing

lib/util.js

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
'use strict';
2323

24+
const { internalBinding } = require('internal/bootstrap/loaders');
2425
const errors = require('internal/errors');
2526
const {
2627
ERR_FALSY_VALUE_REJECTION,
@@ -37,8 +38,8 @@ const {
3738
kRejected,
3839
previewEntries
3940
} = process.binding('util');
41+
const { setPromiseHook } = internalBinding('safe_util');
4042

41-
const { internalBinding } = require('internal/bootstrap/loaders');
4243
const types = internalBinding('types');
4344
Object.assign(types, require('internal/util/types'));
4445
const {
@@ -1464,6 +1465,72 @@ function getSystemErrorName(err) {
14641465
return internalErrorName(err);
14651466
}
14661467

1468+
const promiseEventTypes = [
1469+
'init',
1470+
'resolve',
1471+
'before',
1472+
'after',
1473+
'rejectWithNoHandler',
1474+
'handlerAddedAfterReject',
1475+
'rejectAfterResolved',
1476+
'resolveAfterResolved',
1477+
];
1478+
const promiseHooks = new Set();
1479+
let promiseHookWarned = false;
1480+
let resolveHookRef = 0;
1481+
function promiseHookCallback(type, promise, value) {
1482+
type = promiseEventTypes[type];
1483+
for (const hooks of promiseHooks) {
1484+
try {
1485+
const hook = hooks[type];
1486+
if (typeof hook === 'function') {
1487+
hook(promise, value);
1488+
}
1489+
} catch (e) {
1490+
process.nextTick(() => { throw e; });
1491+
}
1492+
}
1493+
}
1494+
function createPromiseHook(hooks) {
1495+
if (!promiseHookWarned) {
1496+
promiseHookWarned = true;
1497+
process.emitWarning('The util.createPromiseHook API is experimental.',
1498+
'ExperimentalWarning');
1499+
}
1500+
1501+
const hasResolve = 'resolve' in hooks;
1502+
1503+
promiseEventTypes.forEach((type) => {
1504+
const hook = hooks[type];
1505+
if (hook === undefined) {
1506+
return;
1507+
}
1508+
1509+
if (typeof hook !== 'function') {
1510+
throw new ERR_INVALID_ARG_TYPE(`hooks.${type}`, 'function', hook);
1511+
}
1512+
});
1513+
1514+
return {
1515+
enable() {
1516+
promiseHooks.add(hooks);
1517+
if (hasResolve) {
1518+
resolveHookRef++;
1519+
}
1520+
setPromiseHook(promiseHookCallback, resolveHookRef > 0);
1521+
},
1522+
disable() {
1523+
promiseHooks.delete(hooks);
1524+
if (hasResolve) {
1525+
resolveHookRef--;
1526+
}
1527+
setPromiseHook(
1528+
hooks.size === 0 ? null : promiseHookCallback,
1529+
resolveHookRef > 0);
1530+
},
1531+
};
1532+
}
1533+
14671534
// Keep the `exports =` so that various functions can still be monkeypatched
14681535
module.exports = exports = {
14691536
_errnoException: errors.errnoException,
@@ -1505,6 +1572,8 @@ module.exports = exports = {
15051572
TextEncoder,
15061573
types,
15071574

1575+
createPromiseHook,
1576+
15081577
// Deprecated Old Stuff
15091578
debug: deprecate(debug,
15101579
'util.debug is deprecated. Use console.error instead.',

src/bootstrapper.cc

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ using v8::Local;
1616
using v8::MaybeLocal;
1717
using v8::Object;
1818
using v8::Promise;
19-
using v8::PromiseRejectEvent;
20-
using v8::PromiseRejectMessage;
2119
using v8::String;
2220
using v8::Value;
2321

@@ -52,30 +50,21 @@ void SetupNextTick(const FunctionCallbackInfo<Value>& args) {
5250
args.GetReturnValue().Set(ret);
5351
}
5452

55-
void PromiseRejectCallback(PromiseRejectMessage message) {
53+
void PromiseRejectCallback(PromiseHookType type,
54+
Local<Promise> promise,
55+
Local<Value> value,
56+
void* arg) {
5657
static std::atomic<uint64_t> unhandledRejections{0};
5758
static std::atomic<uint64_t> rejectionsHandledAfter{0};
5859

59-
Local<Promise> promise = message.GetPromise();
60-
Isolate* isolate = promise->GetIsolate();
61-
PromiseRejectEvent event = message.GetEvent();
62-
63-
Environment* env = Environment::GetCurrent(isolate);
60+
Environment* env = static_cast<Environment*>(arg);
6461
Local<Function> callback;
65-
Local<Value> value;
6662

67-
if (event == v8::kPromiseRejectWithNoHandler) {
63+
if (type == PromiseHookType::kRejectWithNoHandler) {
6864
callback = env->promise_reject_unhandled_function();
69-
value = message.GetValue();
70-
71-
if (value.IsEmpty())
72-
value = Undefined(isolate);
73-
7465
unhandledRejections++;
75-
} else if (event == v8::kPromiseHandlerAddedAfterReject) {
66+
} else if (type == PromiseHookType::kHandlerAddedAfterReject) {
7667
callback = env->promise_reject_handled_function();
77-
value = Undefined(isolate);
78-
7968
rejectionsHandledAfter++;
8069
} else {
8170
return;
@@ -86,10 +75,13 @@ void PromiseRejectCallback(PromiseRejectMessage message) {
8675
"unhandled", unhandledRejections,
8776
"handledAfter", rejectionsHandledAfter);
8877

78+
if (value.IsEmpty()) {
79+
value = Undefined(env->isolate());
80+
}
8981

9082
Local<Value> args[] = { promise, value };
9183
MaybeLocal<Value> ret = callback->Call(env->context(),
92-
Undefined(isolate),
84+
Undefined(env->isolate()),
9385
arraysize(args),
9486
args);
9587

@@ -99,14 +91,14 @@ void PromiseRejectCallback(PromiseRejectMessage message) {
9991

10092
void SetupPromises(const FunctionCallbackInfo<Value>& args) {
10193
Environment* env = Environment::GetCurrent(args);
102-
Isolate* isolate = env->isolate();
10394

10495
CHECK(args[0]->IsFunction());
10596
CHECK(args[1]->IsFunction());
10697

107-
isolate->SetPromiseRejectCallback(PromiseRejectCallback);
10898
env->set_promise_reject_unhandled_function(args[0].As<Function>());
10999
env->set_promise_reject_handled_function(args[1].As<Function>());
100+
101+
env->AddPromiseRejectHook(PromiseRejectCallback, env);
110102
}
111103

112104
#define BOOTSTRAP_METHOD(name, fn) env->SetMethod(bootstrapper, #name, fn)

0 commit comments

Comments
 (0)