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.bindCurrent() #43033

Closed
wants to merge 1 commit 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
20 changes: 19 additions & 1 deletion doc/api/async_context.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,11 +462,29 @@ changes:
`AsyncResource`.
* `thisArg` {any}

Binds the given function to the current execution context.
Binds the given function to the current execution context using a new
`AsyncResource`.

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

### Static method: `AsyncResource.bindCurrent(fn[, thisArg])`

<!-- YAML
added: REPLACEME
-->

* `fn` {Function} The function to bind to the current execution context.
Copy link
Member

Choose a reason for hiding this comment

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

When would one use bindCurrent instead of bind ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Almost any time you'd think you'd want to use the static bind. It avoids the creation of a new AsyncResource instance, which in many cases isn't actually needed.

Copy link
Member

Choose a reason for hiding this comment

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

Should we explain in the documentation when bind is actually needed?

* `thisArg` {any}

Binds the given function to the current execution context, without creating
an underlying `AsyncResource`, and instead using the current
`executionAsyncResource()`.

The returned function will have an `asyncResource` property referencing
the resource to which the function is bound. Note that this might not be
an instance of `AsyncResource`.

### `asyncResource.bind(fn[, thisArg])`

<!-- YAML
Expand Down
102 changes: 65 additions & 37 deletions lib/async_hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,18 +194,14 @@ class AsyncResource {
}

runInAsyncScope(fn, thisArg, ...args) {
const asyncId = this[async_id_symbol];
emitBefore(asyncId, this[trigger_async_id_symbol], this);

try {
const ret =
ReflectApply(fn, thisArg, args);

return ret;
} finally {
if (hasAsyncIdStack())
emitAfter(asyncId);
}
return runInAsyncScope(
fn,
this[async_id_symbol],
this[trigger_async_id_symbol],
this,
thisArg,
...args
)
}

emitDestroy() {
Expand All @@ -226,31 +222,18 @@ class AsyncResource {

bind(fn, thisArg) {
validateFunction(fn, 'fn');
let bound;
if (thisArg === undefined) {
const resource = this;
bound = function(...args) {
ArrayPrototypeUnshift(args, fn, this);
return ReflectApply(resource.runInAsyncScope, resource, args);
};
} else {
bound = FunctionPrototypeBind(this.runInAsyncScope, this, fn, thisArg);
}
ObjectDefineProperties(bound, {
'length': {
configurable: true,
enumerable: false,
value: fn.length,
writable: false,
},
'asyncResource': {
configurable: true,
enumerable: true,
value: this,
writable: true,
}
});
return bound;
const resource = this;
const asyncId = this[async_id_symbol];
const triggerId = this[trigger_async_id_symbol];
return bind(fn, thisArg, asyncId, triggerId, resource);
}

static bindCurrent(fn, thisArg) {
validateFunction(fn, 'fn');
const resource = executionAsyncResource();
const asyncId = executionAsyncId();
const triggerId = triggerAsyncId();
return bind(fn, thisArg, asyncId, triggerId, resource);
}

static bind(fn, type, thisArg) {
Expand All @@ -259,6 +242,51 @@ class AsyncResource {
}
}

function bind(fn, thisArg, asyncId, triggerAsyncId, asyncResource) {
let bound;
if (thisArg === undefined) {
bound = function(...args) {
ArrayPrototypeUnshift(args, fn, this);
return runInAsyncScope(
fn, asyncId, triggerAsyncId, asyncResource, this, ...args
);
};
} else {
bound = FunctionPrototypeBind(
runInAsyncScope, null, fn, asyncId, triggerAsyncId, asyncResource, thisArg
);
}
ObjectDefineProperties(bound, {
'length': {
configurable: true,
enumerable: false,
value: fn.length,
writable: false,
},
'asyncResource': {
configurable: true,
enumerable: true,
value: asyncResource,
writable: true,
}
});
return bound
}

function runInAsyncScope(fn, asyncId, triggerAsyncId, asyncResource, thisArg, ...args) {
emitBefore(asyncId, triggerAsyncId, asyncResource);

try {
const ret =
ReflectApply(fn, thisArg, args);

return ret;
} finally {
if (hasAsyncIdStack())
emitAfter(asyncId);
}
}

const storageList = [];
const storageHook = createHook({
init(asyncId, type, triggerAsyncId, resource) {
Expand Down
24 changes: 24 additions & 0 deletions test/parallel/test-asyncresource-bind-current.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

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

const thisArg = {};

const asyncId = executionAsyncId();
const fn1 = AsyncResource.bindCurrent(common.mustCall(() => {
assert.strictEqual(executionAsyncId(), asyncId);
}, 2));
const fn2 = AsyncResource.bindCurrent(common.mustCall(function () {
assert.strictEqual(executionAsyncId(), asyncId);
assert.strictEqual(this, thisArg);
}, 2), thisArg);

fn1();
fn2();
AsyncResource.bind(() => {
assert.notStrictEqual(executionAsyncId(), asyncId);
fn1();
fn2();
})();