Skip to content

Commit

Permalink
async_hooks: add AsyncResource.bind utility
Browse files Browse the repository at this point in the history
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 <admin@stephenbelanger.com>
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Andrey Pechkurov <apechkurov@gmail.com>
Reviewed-By: Gerhard Stöbich <deb2001-github@yahoo.de>
  • Loading branch information
jasnell authored and addaleax committed Sep 22, 2020
1 parent e7486d4 commit 45d2f4d
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 5 deletions.
36 changes: 31 additions & 5 deletions doc/api/async_hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,32 @@ class DBQuery extends AsyncResource {
}
```

#### `static AsyncResource.bind(fn[, type])`
<!-- YAML
added: REPLACEME
-->

* `fn` {Function} The function to bind to the current execution context.
* `type` {string} An optional name to associate with the underlying
`AsyncResource`.

Binds the given function to the current execution context.

The returned function will have an `asyncResource` property referencing
the `AsyncResource` to which the function is bound.

#### `asyncResource.bind(fn)`
<!-- YAML
added: REPLACEME
-->

* `fn` {Function} The function to bind to the current `AsyncResource`.

Binds the given function to execute to this `AsyncResource`'s scope.

The returned function will have an `asyncResource` property referencing
the `AsyncResource` to which the function is bound.

#### `asyncResource.runInAsyncScope(fn[, thisArg, ...args])`
<!-- YAML
added: v9.6.0
Expand Down Expand Up @@ -904,12 +930,12 @@ const { createServer } = require('http');
const { AsyncResource, executionAsyncId } = require('async_hooks');

const server = createServer((req, res) => {
const asyncResource = new AsyncResource('request');
// The listener will always run in the execution context of `asyncResource`.
req.on('close', asyncResource.runInAsyncScope.bind(asyncResource, () => {
// Prints: true
console.log(asyncResource.asyncId() === executionAsyncId());
req.on('close', AsyncResource.bind(() => {
// Execution context is bound to the current outer scope.
}));
req.on('close', () => {
// Execution context is bound to the scope that caused 'close' to emit.
});
res.end();
}).listen(3000);
```
Expand Down
24 changes: 24 additions & 0 deletions lib/async_hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

const {
NumberIsSafeInteger,
ObjectDefineProperties,
ReflectApply,
Symbol,
} = primordials;

const {
ERR_ASYNC_CALLBACK,
ERR_ASYNC_TYPE,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ASYNC_ID
} = require('internal/errors').codes;
const { validateString } = require('internal/validators');
Expand Down Expand Up @@ -208,6 +210,28 @@ class AsyncResource {
triggerAsyncId() {
return this[trigger_async_id_symbol];
}

bind(fn) {
if (typeof fn !== 'function')
throw new ERR_INVALID_ARG_TYPE('fn', 'Function', fn);
const ret = this.runInAsyncScope.bind(this, fn);
ObjectDefineProperties(ret, {
'length': {
enumerable: true,
value: fn.length,
},
'asyncResource': {
enumerable: true,
value: this,
}
});
return ret;
}

static bind(fn, type) {
type = type || fn.name;
return (new AsyncResource(type || 'bound-anonymous-fn')).bind(fn);
}
}

const storageList = [];
Expand Down
35 changes: 35 additions & 0 deletions test/parallel/test-asyncresource-bind.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use strict';

const common = require('../common');
const assert = require('assert');
const { AsyncResource, executionAsyncId } = require('async_hooks');

const fn = common.mustCall(AsyncResource.bind(() => {
return executionAsyncId();
}));

setImmediate(() => {
const asyncId = executionAsyncId();
assert.notStrictEqual(asyncId, fn());
});

const asyncResource = new AsyncResource('test');

[1, false, '', {}, []].forEach((i) => {
assert.throws(() => asyncResource.bind(i), {
code: 'ERR_INVALID_ARG_TYPE'
});
});

const fn2 = asyncResource.bind((a, b) => {
return executionAsyncId();
});

assert.strictEqual(fn2.asyncResource, asyncResource);
assert.strictEqual(fn2.length, 2);

setImmediate(() => {
const asyncId = executionAsyncId();
assert.strictEqual(asyncResource.asyncId(), fn2());
assert.notStrictEqual(asyncId, fn2());
});

0 comments on commit 45d2f4d

Please sign in to comment.