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 bindToAsyncScope method to AsyncResource #33736

Closed
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
32 changes: 32 additions & 0 deletions doc/api/async_hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,10 @@ const asyncResource = new AsyncResource(
// * restore the original execution context
asyncResource.runInAsyncScope(fn, thisArg, ...args);

// Return a wrapper function that always runs in the execution context
// of the resource.
asyncResource.bindToAsyncScope(fn);

// Call AsyncHooks destroy callbacks.
asyncResource.emitDestroy();

Expand Down Expand Up @@ -723,6 +727,34 @@ of the async resource. This will establish the context, trigger the AsyncHooks
before callbacks, call the function, trigger the AsyncHooks after callbacks, and
then restore the original execution context.

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

* `fn` {Function} The function to be wrapped.

Return a wrapper function for the supplied function. The returned funtion, when
it is run, will have the same context as if it was run with
`asyncResource.runInAsyncScope()`.

The following is a simple demonstration of `asyncResource.bindToAsyncScope()`:

```js
const { createServer } = require('http');
const { AsyncResource, executionAsyncId } = require('async_hooks');

const server = createServer(function(req, res) {
const asyncResource = new AsyncResource('request');
const asyncId = asyncResource.asyncId();
// The listener will always run in the execution context of `asyncResource`.
req.on('close', asyncResource.bindToAsyncScope(() => {
console.log(executionAsyncId()); // Prints the value of `asyncId`.
}));
res.end();
}).listen(3000);
```

#### `asyncResource.emitDestroy()`

* Returns: {AsyncResource} A reference to `asyncResource`.
Expand Down
7 changes: 7 additions & 0 deletions lib/async_hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,13 @@ class AsyncResource {
}
}

bindToAsyncScope(fn) {
Copy link
Member

Choose a reason for hiding this comment

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

The .length property of the returned function is always 0 here. This may cause problems if the retruned function is used in frameworks like express which use fn.length to decide which type of handler it is (https://github.com/expressjs/express/blob/master/lib/router/layer.js).

Also properties like Symbol(customPromisifyArgs) are stripped which may cause problems.

Both issues can be solved by caller but maybe we should at least document it?

Copy link
Member

Choose a reason for hiding this comment

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

We can also set name and length to meaningful values before returning the function

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch! I'll make sure to address it, if this PR won't be closed.

Copy link
Member

@ronag ronag Jun 5, 2020

Choose a reason for hiding this comment

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

This is a little magical though... documenting it would be my preference

Copy link
Member

@Flarna Flarna Jun 5, 2020

Choose a reason for hiding this comment

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

length, name and Symbol(customPromisifyArgs) are quite easy and most likely harmless if set on wrapped function. The hard part are other properties as they may have special meaning where copy is simply wrong - and not copying either.

const self = this;
return function() {
return self.runInAsyncScope(fn, this, ...arguments);
};
}

emitDestroy() {
if (this[destroyedSymbol] !== undefined) {
this[destroyedSymbol].destroyed = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict';
require('../common');
const assert = require('assert');
const { AsyncResource, executionAsyncId } = require('async_hooks');

const a = new AsyncResource('foobar');

function foo(bar) {
assert.strictEqual(executionAsyncId(), a.asyncId());
return bar;
}

const ret = a.bindToAsyncScope(foo)('baz');
assert.strictEqual(ret, 'baz');