Skip to content

Commit 45d2f4d

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 e7486d4 commit 45d2f4d

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
@@ -733,6 +733,32 @@ class DBQuery extends AsyncResource {
733733
}
734734
```
735735

736+
#### `static AsyncResource.bind(fn[, type])`
737+
<!-- YAML
738+
added: REPLACEME
739+
-->
740+
741+
* `fn` {Function} The function to bind to the current execution context.
742+
* `type` {string} An optional name to associate with the underlying
743+
`AsyncResource`.
744+
745+
Binds the given function to the current execution context.
746+
747+
The returned function will have an `asyncResource` property referencing
748+
the `AsyncResource` to which the function is bound.
749+
750+
#### `asyncResource.bind(fn)`
751+
<!-- YAML
752+
added: REPLACEME
753+
-->
754+
755+
* `fn` {Function} The function to bind to the current `AsyncResource`.
756+
757+
Binds the given function to execute to this `AsyncResource`'s scope.
758+
759+
The returned function will have an `asyncResource` property referencing
760+
the `AsyncResource` to which the function is bound.
761+
736762
#### `asyncResource.runInAsyncScope(fn[, thisArg, ...args])`
737763
<!-- YAML
738764
added: v9.6.0
@@ -904,12 +930,12 @@ const { createServer } = require('http');
904930
const { AsyncResource, executionAsyncId } = require('async_hooks');
905931

906932
const server = createServer((req, res) => {
907-
const asyncResource = new AsyncResource('request');
908-
// The listener will always run in the execution context of `asyncResource`.
909-
req.on('close', asyncResource.runInAsyncScope.bind(asyncResource, () => {
910-
// Prints: true
911-
console.log(asyncResource.asyncId() === executionAsyncId());
933+
req.on('close', AsyncResource.bind(() => {
934+
// Execution context is bound to the current outer scope.
912935
}));
936+
req.on('close', () => {
937+
// Execution context is bound to the scope that caused 'close' to emit.
938+
});
913939
res.end();
914940
}).listen(3000);
915941
```

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');
@@ -208,6 +210,28 @@ class AsyncResource {
208210
triggerAsyncId() {
209211
return this[trigger_async_id_symbol];
210212
}
213+
214+
bind(fn) {
215+
if (typeof fn !== 'function')
216+
throw new ERR_INVALID_ARG_TYPE('fn', 'Function', fn);
217+
const ret = this.runInAsyncScope.bind(this, fn);
218+
ObjectDefineProperties(ret, {
219+
'length': {
220+
enumerable: true,
221+
value: fn.length,
222+
},
223+
'asyncResource': {
224+
enumerable: true,
225+
value: this,
226+
}
227+
});
228+
return ret;
229+
}
230+
231+
static bind(fn, type) {
232+
type = type || fn.name;
233+
return (new AsyncResource(type || 'bound-anonymous-fn')).bind(fn);
234+
}
211235
}
212236

213237
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)