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

lib, doc: fix AsyncResource.bind not using 'this' from the caller by default #42177

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/api/async_context.md
Expand Up @@ -446,6 +446,9 @@ added:
- v14.8.0
- v12.19.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/42177
description: Changed the default when `thisArg` is not provided to use `this` from the caller.
aduh95 marked this conversation as resolved.
Show resolved Hide resolved
- version: v16.0.0
pr-url: https://github.com/nodejs/node/pull/36782
description: Added optional thisArg.
Expand All @@ -468,6 +471,9 @@ added:
- v14.8.0
- v12.19.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/42177
description: Changed the default when `thisArg` is not provided to use `this` from the caller.
aduh95 marked this conversation as resolved.
Show resolved Hide resolved
- version: v16.0.0
pr-url: https://github.com/nodejs/node/pull/36782
description: Added optional thisArg.
Expand Down
24 changes: 15 additions & 9 deletions lib/async_hooks.js
Expand Up @@ -5,6 +5,7 @@ const {
ArrayPrototypeIndexOf,
ArrayPrototypePush,
ArrayPrototypeSplice,
ArrayPrototypeUnshift,
FunctionPrototypeBind,
NumberIsSafeInteger,
ObjectDefineProperties,
Expand Down Expand Up @@ -223,15 +224,20 @@ class AsyncResource {
return this[trigger_async_id_symbol];
}

bind(fn, thisArg = this) {
bind(fn, thisArg) {
validateFunction(fn, 'fn');
const ret =
FunctionPrototypeBind(
this.runInAsyncScope,
this,
fn,
thisArg);
ObjectDefineProperties(ret, {
const runInAsyncScope = this.runInAsyncScope;
rochdev marked this conversation as resolved.
Show resolved Hide resolved
const resource = this;
let bound;
if (thisArg === undefined) {
Copy link
Contributor

@aduh95 aduh95 Mar 3, 2022

Choose a reason for hiding this comment

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

What if the user would like thisArg to be undefined? I think using arguments.length makes more sense here, I don't see a reason to not let undefined be a valid value for thisArg.
If the doc you say when `thisArg` is not provided, not when `thisArg` is `undefined` .

Suggested change
if (thisArg === undefined) {
if (arguments.length !== 1) {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This would prevent ever adding any new arguments without a breaking change to allow undefined as I'm doing right now. I think the tradeoff of undefined being a special value makes more sense.

For example:

asyncResource.bind(fn, undefined, 'foo') // potential future API

Copy link
Member

@targos targos Mar 4, 2022

Choose a reason for hiding this comment

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

We can use if (arguments.length > 1) instead.
Edit: sorry that wouldn't help

Copy link
Member

Choose a reason for hiding this comment

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

the workaround is to wrap the function to give a special value to this:

ar.bind(function (...args) {
  return fn.call(undefined, ...args);
}, undefined, 'foo');

Which seems ok, and is kind of what has to be done in the cases of default arguments anyway. Default arguments in JS are only on undefined so this seems fine.

bound = function(...args) {
ArrayPrototypeUnshift(args, fn, this);
return ReflectApply(runInAsyncScope, resource, args);
};
} else {
bound = FunctionPrototypeBind(runInAsyncScope, resource, fn, thisArg);
}
ObjectDefineProperties(bound, {
'length': {
configurable: true,
enumerable: false,
Expand All @@ -245,7 +251,7 @@ class AsyncResource {
writable: true,
}
});
return ret;
return bound;
}

static bind(fn, type, thisArg) {
Expand Down
7 changes: 6 additions & 1 deletion test/parallel/test-asyncresource-bind.js
Expand Up @@ -41,11 +41,16 @@ const fn3 = asyncResource.bind(common.mustCall(function() {
fn3();

const fn4 = asyncResource.bind(common.mustCall(function() {
assert.strictEqual(this, asyncResource);
assert.strictEqual(this, undefined);
}));
fn4();

const fn5 = asyncResource.bind(common.mustCall(function() {
assert.strictEqual(this, false);
}), false);
fn5();

const fn6 = asyncResource.bind(common.mustCall(function() {
assert.strictEqual(this, 'test');
}));
fn6.call('test');