Skip to content

Commit 16aa927

Browse files
jasnelladdaleax
authored andcommitted
async_hooks: add AsyncResource.bind utility
Creates an internal AsyncResource and binds a function to it, ensuring that the function is invoked within execution context in which bind was called. PR-URL: #34574 Reviewed-By: Stephen Belanger <[email protected]> Reviewed-By: Gus Caplan <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Andrey Pechkurov <[email protected]> Reviewed-By: Gerhard Stöbich <[email protected]>
1 parent ddd339f commit 16aa927

File tree

3 files changed

+90
-5
lines changed

3 files changed

+90
-5
lines changed

doc/api/async_hooks.md

+31-5
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,32 @@ class DBQuery extends AsyncResource {
727727
}
728728
```
729729

730+
#### `static AsyncResource.bind(fn[, type])`
731+
<!-- YAML
732+
added: REPLACEME
733+
-->
734+
735+
* `fn` {Function} The function to bind to the current execution context.
736+
* `type` {string} An optional name to associate with the underlying
737+
`AsyncResource`.
738+
739+
Binds the given function to the current execution context.
740+
741+
The returned function will have an `asyncResource` property referencing
742+
the `AsyncResource` to which the function is bound.
743+
744+
#### `asyncResource.bind(fn)`
745+
<!-- YAML
746+
added: REPLACEME
747+
-->
748+
749+
* `fn` {Function} The function to bind to the current `AsyncResource`.
750+
751+
Binds the given function to execute to this `AsyncResource`'s scope.
752+
753+
The returned function will have an `asyncResource` property referencing
754+
the `AsyncResource` to which the function is bound.
755+
730756
#### `asyncResource.runInAsyncScope(fn[, thisArg, ...args])`
731757
<!-- YAML
732758
added: v9.6.0
@@ -898,12 +924,12 @@ const { createServer } = require('http');
898924
const { AsyncResource, executionAsyncId } = require('async_hooks');
899925

900926
const server = createServer((req, res) => {
901-
const asyncResource = new AsyncResource('request');
902-
// The listener will always run in the execution context of `asyncResource`.
903-
req.on('close', asyncResource.runInAsyncScope.bind(asyncResource, () => {
904-
// Prints: true
905-
console.log(asyncResource.asyncId() === executionAsyncId());
927+
req.on('close', AsyncResource.bind(() => {
928+
// Execution context is bound to the current outer scope.
906929
}));
930+
req.on('close', () => {
931+
// Execution context is bound to the scope that caused 'close' to emit.
932+
});
907933
res.end();
908934
}).listen(3000);
909935
```

lib/async_hooks.js

+24
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
const {
44
NumberIsSafeInteger,
5+
ObjectDefineProperties,
56
ReflectApply,
67
Symbol,
78
} = primordials;
89

910
const {
1011
ERR_ASYNC_CALLBACK,
1112
ERR_ASYNC_TYPE,
13+
ERR_INVALID_ARG_TYPE,
1214
ERR_INVALID_ASYNC_ID
1315
} = require('internal/errors').codes;
1416
const { validateString } = require('internal/validators');
@@ -211,6 +213,28 @@ class AsyncResource {
211213
triggerAsyncId() {
212214
return this[trigger_async_id_symbol];
213215
}
216+
217+
bind(fn) {
218+
if (typeof fn !== 'function')
219+
throw new ERR_INVALID_ARG_TYPE('fn', 'Function', fn);
220+
const ret = this.runInAsyncScope.bind(this, fn);
221+
ObjectDefineProperties(ret, {
222+
'length': {
223+
enumerable: true,
224+
value: fn.length,
225+
},
226+
'asyncResource': {
227+
enumerable: true,
228+
value: this,
229+
}
230+
});
231+
return ret;
232+
}
233+
234+
static bind(fn, type) {
235+
type = type || fn.name;
236+
return (new AsyncResource(type || 'bound-anonymous-fn')).bind(fn);
237+
}
214238
}
215239

216240
const storageList = [];
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const { AsyncResource, executionAsyncId } = require('async_hooks');
6+
7+
const fn = common.mustCall(AsyncResource.bind(() => {
8+
return executionAsyncId();
9+
}));
10+
11+
setImmediate(() => {
12+
const asyncId = executionAsyncId();
13+
assert.notStrictEqual(asyncId, fn());
14+
});
15+
16+
const asyncResource = new AsyncResource('test');
17+
18+
[1, false, '', {}, []].forEach((i) => {
19+
assert.throws(() => asyncResource.bind(i), {
20+
code: 'ERR_INVALID_ARG_TYPE'
21+
});
22+
});
23+
24+
const fn2 = asyncResource.bind((a, b) => {
25+
return executionAsyncId();
26+
});
27+
28+
assert.strictEqual(fn2.asyncResource, asyncResource);
29+
assert.strictEqual(fn2.length, 2);
30+
31+
setImmediate(() => {
32+
const asyncId = executionAsyncId();
33+
assert.strictEqual(asyncResource.asyncId(), fn2());
34+
assert.notStrictEqual(asyncId, fn2());
35+
});

0 commit comments

Comments
 (0)