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
Improve async dispose fallback #16409
base: main
Are you sure you want to change the base?
Changes from all commits
43eed64
61c830e
e57be3c
8fb2a72
350dd2f
947a930
c46ba2d
dcab815
8869938
c3a7acf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,24 @@ | ||
/* @minVersion 7.23.9 */ | ||
|
||
const enum DisposeFlag { | ||
// set when the dispose function is a [Symbol.asyncDispose] method | ||
ASYNC_DISPOSE = 1, | ||
// alias of AWAIT_USING: set when the resource is an `await using` disposable | ||
ASYNC_DISPOSE_MASK = 2, | ||
} | ||
|
||
const enum DisposeKind { | ||
USING_DISPOSE = 0, | ||
// AWAIT_FLAG | ||
AWAIT_USING_DISPOSE = 2, | ||
// Flag.AWAIT_USING | Flag.ASYNC_DISPOSE | ||
AWAIT_USING_ASYNC_DISPOSE = 3, | ||
} | ||
|
||
type Stack = { | ||
v?: any; | ||
d: null | undefined | (() => any); | ||
a: boolean; | ||
k: DisposeKind; | ||
}; | ||
|
||
export default function _usingCtx() { | ||
|
@@ -30,18 +45,20 @@ export default function _usingCtx() { | |
// core-js-pure uses Symbol.for for polyfilling well-known symbols | ||
if (isAwait) { | ||
var dispose = | ||
value[Symbol.asyncDispose || Symbol.for("Symbol.asyncDispose")]; | ||
value[Symbol.asyncDispose || Symbol.for("Symbol.asyncDispose")], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer to unify them into something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The disposal function may be okay. The proposed error here is for the case when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right! I changed it to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just realized that I think we can add a section to the plugin docs about how to use this plugin with core-js 3 or other polyfill providers. Integration tests with polyfill providers should be added as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In chrome 122 or any browsers without const disposable = { [Symbol.dispose]() {} } will be evaluated as if it were defined as const disposable = { undefined() {} } If users have properly polyfilled const disposable = { [Symbol.for("Symbol.dispose")]() {} } The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense! But given that this exists in all symbol-related helpers, I'm a little hesitant that we should include it by default. |
||
kind = DisposeKind.AWAIT_USING_ASYNC_DISPOSE; | ||
} | ||
if (dispose == null) { | ||
if (dispose === undefined) { | ||
dispose = value[Symbol.dispose || Symbol.for("Symbol.dispose")]; | ||
kind &= DisposeFlag.ASYNC_DISPOSE_MASK; | ||
} | ||
if (typeof dispose !== "function") { | ||
throw new TypeError(`Property [Symbol.dispose] is not a function.`); | ||
throw new TypeError("Object is not disposable."); | ||
} | ||
stack.push({ v: value, d: dispose, a: isAwait }); | ||
stack.push({ v: value, d: dispose, k: kind }); | ||
} else if (isAwait) { | ||
// provide the nullish `value` as `d` for minification gain | ||
stack.push({ d: value, a: isAwait }); | ||
stack.push({ d: value, k: DisposeKind.AWAIT_USING_DISPOSE }); | ||
} | ||
return value; | ||
} | ||
|
@@ -62,8 +79,11 @@ export default function _usingCtx() { | |
try { | ||
var resource, | ||
disposalResult = resource.d && resource.d.call(resource.v); | ||
if (resource.a) { | ||
return Promise.resolve(disposalResult).then(next, err); | ||
if (resource.k) { | ||
return Promise.resolve( | ||
// do not await the promise returned from the sync @@dispose | ||
resource.k & DisposeFlag.ASYNC_DISPOSE && disposalResult, | ||
).then(next, err); | ||
} | ||
} catch (e) { | ||
return err(e); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,24 @@ | ||
return expect(async function () { | ||
await using foo = { [Symbol.asyncDispose || Symbol.for("Symbol.asyncDispose")]: 3 }; | ||
}()).rejects.toThrow(TypeError); | ||
expect( | ||
(async function () { | ||
await using foo = { | ||
[Symbol.asyncDispose || Symbol.for("Symbol.asyncDispose")]: 3, | ||
}; | ||
})() | ||
).rejects.toThrow(TypeError); | ||
|
||
expect( | ||
(async function () { | ||
await using foo = { | ||
[Symbol.asyncDispose || Symbol.for("Symbol.asyncDispose")]: 3, | ||
}; | ||
})() | ||
).rejects.toThrow("Object is not disposable."); | ||
|
||
expect( | ||
(async function () { | ||
await using foo = { | ||
[Symbol.asyncDispose || Symbol.for("Symbol.asyncDispose")]: null, | ||
[Symbol.dispose || Symbol.for("Symbol.dispose")]() {}, | ||
}; | ||
})() | ||
).rejects.toThrow("Object is not disposable."); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
return (async () => { | ||
const log = []; | ||
|
||
const promiseDispose = new Promise((resolve, _) => { | ||
setTimeout(() => { | ||
log.push("y dispose promise body"); | ||
resolve(); | ||
}, 0); | ||
}); | ||
|
||
{ | ||
await using x = { | ||
[Symbol.asyncDispose || Symbol.for("Symbol.asyncDispose")]() { | ||
log.push("x asyncDispose body"); | ||
}, | ||
}; | ||
await using y = { | ||
[Symbol.dispose || Symbol.for("Symbol.dispose")]() { | ||
log.push("y dispose body"); | ||
return promiseDispose; | ||
}, | ||
}; | ||
} | ||
|
||
log.push("body"); | ||
|
||
await promiseDispose; | ||
|
||
expect(log).toEqual([ | ||
"y dispose body", | ||
"x asyncDispose body", | ||
"body", | ||
"y dispose promise body", | ||
]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In current main, the |
||
})(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I renamed the enums so that hopefully two-year-later me will not get lost when reviewing the helper.