Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

async_hooks: add AsyncResource.bind utility #34574

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 31 additions & 5 deletions doc/api/async_hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,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 @@ -900,12 +926,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 @@ -211,6 +213,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, {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think both properties should use configurable: true.

'length': {
enumerable: true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think length should use enumerable: false

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no points will be deducted for using a temporary variable :P

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parentheses unnecessary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah but stylistically I prefer them (more readable in my opinion)

}
}

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());
});