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‎

Lines changed: 31 additions & 5 deletions
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
constserver=createServer((req, res) =>{
907-
constasyncResource=newAsyncResource('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‎

Lines changed: 24 additions & 0 deletions
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
returnthis[trigger_async_id_symbol];
210212
}
213+
214+
bind(fn){
215+
if(typeoffn!=='function')
216+
thrownewERR_INVALID_ARG_TYPE('fn','Function',fn);
217+
constret=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+
returnret;
229+
}
230+
231+
staticbind(fn,type){
232+
type=type||fn.name;
233+
return(newAsyncResource(type||'bound-anonymous-fn')).bind(fn);
234+
}
211235
}
212236

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

0 commit comments

Comments
(0)