add @@asyncIterator to ReadableStream#954
add @@asyncIterator to ReadableStream#954devsnek wants to merge 7 commits intowhatwg:masterfrom devsnek:feature/rs-asynciterator
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.
This copy-pasted text seems not as useful as the old text.
index.bs
Outdated
| </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> |
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.
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.
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.
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.
MattiasBuelens
left a comment
There was a problem hiding this comment.
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.
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.
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.
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.
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? |
MattiasBuelens
left a comment
There was a problem hiding this comment.
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.
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.
Same as above, this should return a rejected promise.
MattiasBuelens
left a comment
There was a problem hiding this comment.
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.
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.
Should be this._preventCancel.
There was a problem hiding this comment.
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.
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.
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.
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.
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