-
Notifications
You must be signed in to change notification settings - Fork 161
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
add @@asyncIterator to ReadableStream #954
Conversation
index.bs
Outdated
|
||
This functionality is especially useful for creating abstractions that desire the ability to consume a stream in its | ||
entirety. By getting a reader for the stream, you can ensure nobody else can interleave reads with yours or cancel | ||
the stream, which would interfere with your abstraction. |
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.
This copy-pasted text seems not as useful as the old text.
index.bs
Outdated
@@ -866,6 +868,84 @@ option. If <code><a for="underlying source">type</a></code> is set to <code>unde | |||
</code></pre> | |||
</div> | |||
|
|||
<!-- Bikeshed doesn't let us mark this up correctly: https://github.com/tabatkins/bikeshed/issues/1344 --> | |||
<h5 id="rs-asynciterator" for="ReadableStream">[@@asyncIterator]({ <var>preventCancel</var> = false } = {})</h5> |
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.
What happened to getIterator()?
index.bs
Outdated
lt="ReadableStreamAsyncIteratorPrototype">ReadableStreamAsyncIteratorPrototype</h3> | ||
|
||
{{ReadableStreamAsyncIteratorPrototype}} is an ordinary object that is used by {{ReadableStream/[@@asyncIterator]()}} to | ||
construct the objects it returns. Instances of {{ReadableStreamAsyncIteratorPrototype}} implmenet the {{AsyncIterator}} |
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.
Typo "implement" (maybe my fault).
index.bs
Outdated
exception. | ||
1. Let _result_ be ! ReadableStreamReaderGenericCancel(_reader_, _value_). | ||
1. Otherwise, | ||
1. Let _result_ be ! ReadableStreamCreateReadResult(_value_, *true*, *true*). |
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.
This needs to be a promise resolved with...
index.bs
Outdated
1. Otherwise, | ||
1. Let _result_ be _value_. | ||
1. Perform ! ReadableStreamReaderGenericRelease(_reader_). | ||
1. Return <a>a promise resolved with</a> ! ReadableStreamCreateReadResult(_result_, *true*, *true*). |
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.
So now if preventCancel
is false return()
will give a promise for { value: promise for undefined, done: true }
. Whereas if it is true, return()
will give a promise for { value: value, done: true }
. It should give the latter in both cases.
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.
Looks good to me! Just needs reference implementation and tests. Also good to get @ricea's sign-off, of course.
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.
Still some holes left in return()
.
1. Perform ! ReadableStreamReaderGenericRelease(_reader_). | ||
1. Return the result of <a>transforming</a> _result_ by a fulfillment handler that returns ! | ||
ReadableStreamCreateReadResult(_value_, *true*, *true*). | ||
1. Perform ! ReadableStreamReaderGenericRelease(_reader_). |
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.
This should reject if reader.[[ownerReadableStream]]
is undefined
. Otherwise, we may fail an assert in ReadableStreamReaderGenericRelease
.
We should probably just move step 3a to before step 3.
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 think it's impossible for it to be undefined
. User code would have to get access to [[reader]] to call releaseLock()
, and it can't. So this should be an assert.
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.
It's not impossible for it to be undefined
, but you really have to go out of your way to break it:
const iterator = readable.getIterator();
iterator.return(); // releases lock
iterator.return(); // boom!
1. Perform ! ReadableStreamReaderGenericRelease(_reader_). | ||
1. Return the result of <a>transforming</a> _result_ by a fulfillment handler that returns ! | ||
ReadableStreamCreateReadResult(_value_, *true*, *true*). | ||
1. Perform ! ReadableStreamReaderGenericRelease(_reader_). |
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.
What happens when there are pending read requests? See previous discussion.
Normally, ReadableStreamDefaultReader.releaseLock()
checks whether this.[[readRequests]]
is empty. However, this implementation uses ReadableStreamReaderGenericRelease
directly without going through the same checks as releaseLock
.
Mattias found problems I did not, oops
Let's work on a test plan together to guide @devsnek. Here's a start:
Can anyone think of something else that might be missing? |
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.
One more thing we missed in the spec text: next()
and return()
should return rejected promises instead of throwing. @domenic got it right in their proposed test plan.
index.bs
Outdated
<h4 id="rs-asynciterator-prototype-next" method for="ReadableStreamAsyncIteratorPrototype">next()</h4> | ||
|
||
<emu-alg> | ||
1. If ! IsReadableStreamAsyncIterator(*this*) is *false*, throw a *TypeError* exception. |
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.
This should return a rejected promise, rather than throw an exception. From this comment by @domenic:
Calling next() or return() on non-ReadableStreamDefaultReaderAsyncIterator instances rejects with TypeError (there are existing brand check tests you can add to)
index.bs
Outdated
for="ReadableStreamAsyncIteratorPrototype">return( <var>value</var> )</h4> | ||
|
||
<emu-alg> | ||
1. If ! IsReadableStreamAsyncIterator(*this*) is *false*, throw a *TypeError* exception. |
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.
Same as above, this should return a rejected promise.
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.
One tiny typo left. Other than that, spec text looks good. 👍
index.bs
Outdated
<h4 id="rs-asynciterator-prototype-next" method for="ReadableStreamAsyncIteratorPrototype">next()</h4> | ||
|
||
<emu-alg> | ||
1. If ! IsReadableStreamAsyncIterator(*this*) is *false*, <a>a promise rejected with</a> *TypeError* exception. |
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 word "return" is missing in this sentence.
return Promise.reject(streamAsyncIteratorBrandCheckException('next')); | ||
} | ||
const reader = this._asyncIteratorReader; | ||
if (this.preventCancel === false) { |
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.
Should be this._preventCancel
.
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.
Good catch! This would have been an embarrassing bug. 😛
} | ||
const reader = this._asyncIteratorReader; | ||
if (reader._ownerReadableStream === undefined) { | ||
return Promise.reject(readerLockException('next')); |
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.
Maybe this should be readerLockException('iterate')
?
"Cannot next a stream using a released reader" doesn't make much sense.
const reader = this._asyncIteratorReader; | ||
if (this.preventCancel === false) { | ||
if (reader._ownerReadableStream === undefined) { | ||
return Promise.reject(readerLockException('next')); |
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.
This should not be 'next'
. Maybe 'finish iterating'
makes sense?
index.bs
Outdated
1. If ! IsReadableStreamAsyncIterator(*this*) is *false*, return <a>a promise rejected with</a> *TypeError* exception. | ||
1. Let _reader_ be *this*.[[asyncIteratorReader]]. | ||
1. If *this*.[[preventCancel]] is *false*, then: | ||
1. If _reader_.[[ownerReadableStream]] is *undefined*, return <a>a promise rejected with</a> a *TypeError* |
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.
These two steps are in both branches of the if statement and so should be moved up above it.
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.
This should also be changed in the reference implementation.
We also need a test to verify that it inherits from %AsyncIteratorPrototype%. I don't know the best way to do that. |
Looks good. Are you working on some web platform tests? |
@ricea yeah, its coming along. i'll have a pr up this weekend hopefully. |
@devsnek How is it going? Could you upload a work-in-progress PR of the tests so I can help out? |
@ricea sorry its been taking me so long to finish this up. here's what i've done so far: web-platform-tests/wpt#13362 |
@devsnek Thanks, I had a quick look and it looks good. I will pick through it in more detail tomorrow. |
I'm really excited about this! Looking forward to it landing. 🍻 |
@devsnek I can take this over if you are busy. Please let me know. |
Oh right, I previously said that I could also pick this up in my free time if needed. But if you want to take it over, that's also fine by me, then I'll help with the review instead. 🙂 |
@ricea @MattiasBuelens feel free to take over |
Okay, well since you offered first, you should have first opportunity to do it. If you're busy I'll pick it up in a week or so. |
Thanks! 😄 I think I can look into it over the weekend. Just to check: I should branch off from web-platform-tests/wpt#13362, and then submit a new PR, right? Since I obviously can't push commits to a branch I don't own. And if further changes are needed on the spec or reference implementation, those can also go into a new PR? |
Sounds good to me. |
Closes #778
Preview | Diff