-
Notifications
You must be signed in to change notification settings - Fork 164
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
Make async iterator return() and next() more like async generators #891
Conversation
Previously, we would only set the async iterator's "ongoing promise" to null when the async iterator next steps succeeded. This meant that calling return() after a failed next() would not call the async iterator return steps, thus potentially preventing the cleanup of any associated resources. This changes the spec to reset the ongoing promise after the async iterator next steps complete, regardless of whether they succeed or fail.
1. Let |rejectSteps| be the following steps, given |reason|: | ||
1. Set |object|'s [=default asynchronous iterator object/ongoing promise=] to | ||
null. | ||
1. [=ECMAScript/Throw=] |reason|. |
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.
A few more things to think about.
try {
const a = await it.next(); // eventually rejected
} catch {
const b = await it.next();
}
Async generators (and also sync generators) would set b
to { value: undefined, done: true }
. We could do the same by just setting the is finished flag to true, which would give us the invariant that getting the next iteration result will never be called after it has rejected a promise, which is something I think spec authors could come to expect.
Alternatively, we could consider a “one bad apple spoils the bunch” semantic, where b
gets rejected. I think we should stick with the async generator behavior though.
Going further, consider this case:
const a = it.next(); // note: no await here
const b = it.next();
It seems like b
should be settled in the same way as b
above, as the timing of when it.next()
is called shouldn't have any bearing on its result. But then this would require us to keep some unsettled promise set on object's target, which brings about additional complexity. Async generators has something analogous through the [[AsyncGeneratorQueue]] internal slot; see spec.
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 like these ideas, and I will work to align the spec with generators in the way you suggest. It's good that we're getting this review coverage now that these things are being used seriously in specs, and implemented (albeit only implemented in webidl2js so far).
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've attempted to implement semantics that pass these test cases in the latest commit of jsdom/webidl2js#224. If you agree that the changes there are reasonable, I'll port them over to spec text form.
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.
Also feel free to peruse the tests added in web-platform-tests/wpt#22982 (commits)
Alright, I've ported over the changes from the jsdom/webidl2js implementation, so this should be ready for re-review. I've updated the title and original post to reflect the broader scope of this PR; feel free to critique that as well in case I missed a detail or got something wrong. |
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.
LGTM other than the one tiny thing.
index.bs
Outdated
1. Let |result| be [$CreateIterResultObject$](<emu-val>undefined</emu-val>, | ||
<emu-val>true</emu-val>). |
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.
The webidl2js implementation uses value as the first parameter rather than undefined, I believe correctly.
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.
Ah yep, and that's what async generators do. Great catch; thank you!
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'll rubberstamp for Timothy. And going forward that won't be needed anymore as he has commit access now. (And should probably be listed as co-editor?)
Previously, we would only set the async iterator's "ongoing promise" to null when the async iterator next steps succeeded. This meant that calling return() after a failed next() would not call the async iterator return steps, thus potentially preventing the cleanup of any associated resources. This changes the spec to reset the ongoing promise after the async iterator next steps complete, regardless of whether they succeed or fail.
Credit to @TimothyGu for finding this in jsdom/webidl2js#224 (comment). (Timothy, please review!)
Tests for this are added in the Streams context in various commits of web-platform-tests/wpt#22982.
Preview | Diff