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

add @@asyncIterator to ReadableStreamDefaultReader #950

Closed
wants to merge 3 commits into from
Closed

add @@asyncIterator to ReadableStreamDefaultReader #950

wants to merge 3 commits into from

Conversation

devsnek
Copy link

@devsnek devsnek commented Aug 18, 2018

Closes #778

probably needs a lot of editorial :)


Preview | Diff

index.bs Outdated
</tr>
</table>

<h5 id="default-reader-asynciterator-prototype-next" method for="ReadableStreamDefaultReaderAsyncIteratorPrototype">
Copy link
Member

Choose a reason for hiding this comment

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

This and return() should be <h4>'s.

index.bs Outdated
@@ -1402,6 +1405,87 @@ lt="ReadableStreamDefaultReader(stream)">new ReadableStreamDefaultReader(<var>st
1. Perform ! ReadableStreamReaderGenericRelease(*this*).
</emu-alg>

<h5 id="default-reader-iterator" method for="ReadableStreamDefaultReader">
iterator({ cancel = true } = {})
Copy link
Member

@TimothyGu TimothyGu Aug 21, 2018

Choose a reason for hiding this comment

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

There is no precedent for an iterator method anywhere in the platform and I'm reluctant to start that now. Just having @@asyncIterator is good enough IMO. (@@asyncIterator can take a parameter too.)

Copy link
Author

Choose a reason for hiding this comment

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

iterator() came from the linked issue, i'm unsure what to do here.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah we should have this method. But we should flip it to preventCancel = true. https://w3ctag.github.io/design-principles/#prefer-dict-to-bool third paragraph.

Copy link
Member

Choose a reason for hiding this comment

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

How about "getIterator" or "iterate"? The first provides consistency with the existing "getReader". I do prefer a name with a verb in it.

Choose a reason for hiding this comment

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

@domenic The default for this would be preventCancel = false, right?

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, now I'm thinking "values()" for symmetry with sync iterators?

Copy link
Member

Choose a reason for hiding this comment

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

OTOH that has the potential to be confused as a sync iterator…

index.bs Outdated

<emu-alg>
1. Let _O_ be *this* value.
1. If Type(_O_) is not Object, throw a *TypeError* exception.
Copy link
Member

Choose a reason for hiding this comment

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

  1. This needs to check if O has a [[Reader]] internal slot. Otherwise O might not be a ReadableStreamDefaultReaderAsyncIterator. It might be worthwhile to have an IsReadableStreamDefaultReaderAsyncIterator abstract operation.
  2. This spec uses this rather "this value" (the ECMA-262 convention). So you could just do Type(this).

Ditto below for cancel.

index.bs Outdated
<emu-alg>
1. Let _O_ be *this* value.
1. If Type(_O_) is not Object, throw a *TypeError* exception.
1. Let _reader_ be O.[[Reader]].
Copy link
Member

Choose a reason for hiding this comment

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

Missing _ for O.

index.bs Outdated
1. Let _O_ be *this* value.
1. If Type(_O_) is not Object, throw a *TypeError* exception.
1. Let _reader_ be O.[[Reader]].
1. Let _read_ be ? GetMethod(_reader_, "read").
Copy link
Member

Choose a reason for hiding this comment

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

"read"

Ditto below for cancel.

index.bs Outdated
1. If Type(_O_) is not Object, throw a *TypeError* exception.
1. Let _reader_ be O.[[Reader]].
1. Let _read_ be ? GetMethod(_reader_, "read").
1. Return ? Call(_read_, _reader_);
Copy link
Member

Choose a reason for hiding this comment

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

s/;/.

index.bs Outdated

<h3 id="default-reader-asynciterator-prototype" interface lt="ReadableStreamDefaultReaderAsyncIteratorPrototype">
ReadableStreamDefaultReaderAsyncIteratorPrototype
</h3>
Copy link
Member

Choose a reason for hiding this comment

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

It should be written somewhere that this inherits from %AsyncIteratorPrototype%.

Copy link
Author

Choose a reason for hiding this comment

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

yea ik... i wasn't sure how/where to do that.

Choose a reason for hiding this comment

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

is this a must for implementers? Or is it how things are spec'ed?

Copy link
Author

Choose a reason for hiding this comment

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

@mcollina all async iterators should inherit from (have [[Prototype]] set to) %AsyncIteratorPrototype%, but it doesn't happen by default. i added it in my last commit.

Choose a reason for hiding this comment

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

@devsnek reading from https://tc39.github.io/ecma262/#sec-asynciteratorprototype, it does not seem to be mandatory for implementers. I'm just trying to understand if there are any benefits in doing so.

Copy link
Member

@TimothyGu TimothyGu Aug 21, 2018

Choose a reason for hiding this comment

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

@mcollina Yes, it is mandatory; and yes, there are benefits. Having it would allow

for await (const chunk of stream.getReader().getIterator()) {
}

in addition to just

for await (const chunk of stream.getReader()) {
}

This is fully consistent with how sync iterators operate, that you can do Array.from(map.entries()) in addition to just Array.from(map).

Additionally, it would allow ECMA-262 to extend %AsyncIteratorPrototype% and the improvements would directly reflect on streams.

Copy link
Member

@TimothyGu TimothyGu Aug 21, 2018

Choose a reason for hiding this comment

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

Also, you can get the %AsyncIteratorPrototype% through Object.getPrototypeOf(Object.getPrototypeOf((async function* () {}).prototype)). as you have found out.

index.bs Outdated
</h5>

<div class="note">
The <code>iterator</code> method returns an async iterator which can be used to consume the stream. The return method
Copy link
Member

Choose a reason for hiding this comment

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

The return method

index.bs Outdated

<div class="note">
The <code>iterator</code> method returns an async iterator which can be used to consume the stream. The return method
of this iterator will optionally release or cancel this reader.
Copy link
Member

Choose a reason for hiding this comment

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

  1. this iterator object
  2. Link release and maybe cancel too?

index.bs Outdated

<h3 id="default-reader-asynciterator-prototype" interface lt="ReadableStreamDefaultReaderAsyncIteratorPrototype">
ReadableStreamDefaultReaderAsyncIteratorPrototype
</h3>

Choose a reason for hiding this comment

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

is this a must for implementers? Or is it how things are spec'ed?

index.bs Outdated
@@ -1402,6 +1405,87 @@ lt="ReadableStreamDefaultReader(stream)">new ReadableStreamDefaultReader(<var>st
1. Perform ! ReadableStreamReaderGenericRelease(*this*).
</emu-alg>

<h5 id="default-reader-iterator" method for="ReadableStreamDefaultReader">
iterator({ cancel = true } = {})

Choose a reason for hiding this comment

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

@domenic The default for this would be preventCancel = false, right?

if (O._cancel === true) {
const reader = O._reader;
const cancel = reader.cancel;
cancel.call(reader);

Choose a reason for hiding this comment

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

I think it should be return cancel.call(reader);

Copy link
Member

Choose a reason for hiding this comment

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

The spec says "Perform ? Call(…)". There's no return anywhere.

cancel.call(reader);
}
}
}, Object.getPrototypeOf(Object.getPrototypeOf(async function* () {}).prototype));

Choose a reason for hiding this comment

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

What benefit does it add setting the prototype to this?

Copy link
Member

Choose a reason for hiding this comment

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

index.bs Outdated

<emu-alg>
1. Let _iterator_ be ? GetMethod(*this*, "iterator").
1. Return ? Call(_iterator_, *this*).
Copy link
Member

@TimothyGu TimothyGu Aug 21, 2018

Choose a reason for hiding this comment

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

This is not idiomatic and not how existing iterators work in ECMAScript and Web IDL. It in fact should be equal to the iterator method (or whatever it ends up being called). See how [].values === [][Symbol.iterator], Map.prototype.entries === Map.prototype[Symbol.iterator], and URLSearchParams.prototype.entries === URLSearchParams.prototype[Symbol.iterator].

Copy link
Member

Choose a reason for hiding this comment

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

Yeah for spec style we should follow https://tc39.github.io/ecma262/#sec-map.prototype-@@iterator I guess.

Copy link
Author

Choose a reason for hiding this comment

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

agreed, i just wasn't sure how to write that out in bs. thanks for the link.

if (O._cancel === true) {
const reader = O._reader;
const cancel = reader.cancel;
cancel.call(reader);
Copy link
Member

Choose a reason for hiding this comment

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

This does not implement GetMethod fully. Additionally I'd prefer using Reflect.apply().

Copy link
Member

Choose a reason for hiding this comment

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

We should put a full GetMethod implementation in helpers.js.

}

const ReadableStreamDefaultReaderAsyncIteratorPrototype = Object.setPrototypeOf({
_reader: undefined,
_cancel: undefined,
Copy link
Member

Choose a reason for hiding this comment

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

Why does the prototype have these properties? The prototype is not an instance of ReadableStreamDefaultReaderAsyncIterator.

Copy link
Member

@domenic domenic left a comment

Choose a reason for hiding this comment

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

Great start, and sorry for the delay in reviewing!

index.bs Outdated

<emu-alg>
1. Let _iterator_ be ? GetMethod(*this*, "iterator").
1. Return ? Call(_iterator_, *this*).
Copy link
Member

Choose a reason for hiding this comment

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

Yeah for spec style we should follow https://tc39.github.io/ecma262/#sec-map.prototype-@@iterator I guess.

index.bs Outdated

<h3 id="default-reader-asynciterator-prototype" interface lt="ReadableStreamDefaultReaderAsyncIteratorPrototype">
ReadableStreamDefaultReaderAsyncIteratorPrototype
</h3>
Copy link
Member

Choose a reason for hiding this comment

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

Style nit: this introduces spaces between the > and R and the e and the <. No good. Same throughout various places in the patch.

index.bs Outdated
</h4>

<emu-alg>
1. If ! IsReadableStreamDefaultReaderAsyncIteratorPrototype(*this*) is *false*, throw a *TypeError* exception.
Copy link
Member

Choose a reason for hiding this comment

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

IsReadableStreamDefaultReaderAsyncIteratorPrototype doesn't seem to be defined?

index.bs Outdated

<h4 id="default-reader-asynciterator-prototype-return" method for="ReadableStreamDefaultReaderAsyncIteratorPrototype">
return()
</h4>
Copy link
Member

Choose a reason for hiding this comment

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

So apparently async iterator return methods should take a value argument and return a promise for { done: true, value }.

You'll want to return the result of transforming the call to cancel() by a function that creates and returns the { done: true, value } tuple (probably using ReadableStreamCreateReadResult for consistency).

@@ -700,8 +700,52 @@ class ReadableStreamDefaultReader {

ReadableStreamReaderGenericRelease(this);
}

iterator({ cancel = true } = {}) {
const O = this;
Copy link
Member

Choose a reason for hiding this comment

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

To follow the spec more exactly, don't do the O aliasing. Here and everywhere below.

index.bs Outdated
<emu-alg>
1. If ! IsReadableStreamDefaultReader(*this*) is *false*, throw a *TypeError* exception.
1. If *this*.[[ownerReadableStream]] is *undefined*, throw a *TypeError* exception.
1. Let _iterator_ be ? ObjectCreate(`<a idl>ReadableStreamDefaultReaderAsyncIteratorPrototype</a>`, « [[Reader]],
Copy link
Member

Choose a reason for hiding this comment

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

This should be !, not ?, I think.

if (O._cancel === true) {
const reader = O._reader;
const cancel = reader.cancel;
cancel.call(reader);
Copy link
Member

Choose a reason for hiding this comment

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

We should put a full GetMethod implementation in helpers.js.

index.bs Outdated
</h4>

<emu-alg>
1. If ! IsReadableStreamDefaultReaderAsyncIteratorPrototype(*this*) is *false*, throw a *TypeError* exception.
Copy link
Collaborator

Choose a reason for hiding this comment

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

next() and return() should reject the returned promise instead of throwing an exception. This also means turning any exception thrown from GetMethod and Call into rejections.

index.bs Outdated
@@ -1402,6 +1405,92 @@ lt="ReadableStreamDefaultReader(stream)">new ReadableStreamDefaultReader(<var>st
1. Perform ! ReadableStreamReaderGenericRelease(*this*).
</emu-alg>

<h5 id="default-reader-iterator" method for="ReadableStreamDefaultReader">
iterator({ cancel = true } = {})</h5>
Copy link
Member

Choose a reason for hiding this comment

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

Just don't worry about the line length and put it on the same line as <h5>.

index.bs Outdated

<h5 id="default-reader-@@asynciterator" for="ReadableStreamDefaultReader">[@@asyncIterator]()</h5>

<div class="note">
Copy link
Member

Choose a reason for hiding this comment

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

<p class="note">

index.bs Outdated
</div>

The initial value of the <code>@@asyncIterator</code> method is the same function object as the initial value of the
<code>iterator</code> method.
Copy link
Member

Choose a reason for hiding this comment

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

This should be linked. Also it should be made explicit which iterator method we are talking about (which object it belongs to), if we have more than one in the future.

index.bs Outdated
@@ -1402,6 +1405,92 @@ lt="ReadableStreamDefaultReader(stream)">new ReadableStreamDefaultReader(<var>st
1. Perform ! ReadableStreamReaderGenericRelease(*this*).
</emu-alg>

<h5 id="default-reader-iterator" method for="ReadableStreamDefaultReader">
iterator({ cancel = true } = {})</h5>
Copy link
Member

Choose a reason for hiding this comment

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

My point about the naming remains. I still would like to see it as getIterator() or iterate().

index.bs Outdated
<h5 id="default-reader-iterator" method for="ReadableStreamDefaultReader">
iterator({ cancel = true } = {})</h5>

<div class="note">
Copy link
Member

Choose a reason for hiding this comment

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

Use a paragraph element.

index.bs Outdated
<var>x</var> )</h4>

<emu-alg>
1. If Type(_x_) is not Object, return *false*.
Copy link
Member

Choose a reason for hiding this comment

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

Per ECMAScript convention, objects usually use a single uppercase letter or a camel-cased phrase as variable name. In this case I believe argument would suffice.

index.bs Outdated

<emu-alg>
1. If Type(_x_) is not Object, return *false*.
1. If _x_ does not have a [[Reader]] and [[Cancel]] internal slots, return *false*.
Copy link
Member

Choose a reason for hiding this comment

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

The "a" seems misplaced.

index.bs Outdated
@@ -1562,6 +1651,16 @@ ReadableStreamBYOBReader(<var>stream</var>)</h4>
1. Return *true*.
</emu-alg>

<h4 id="is-readable-stream-default-reader-asynciterator-prototype">
aoid="IsReadableStreamDefaultReaderAsyncIteratorPrototype" nothrow>IsReadableStreamDefaultReaderAsyncIteratorPrototype (
Copy link
Member

Choose a reason for hiding this comment

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

You are testing for if an object is an AsyncIterator, not if it's an object.

index.bs Outdated
1. If ! IsReadableStreamDefaultReaderAsyncIteratorPrototype(*this*) is *false*, throw a *TypeError* exception.
1. Let _reader_ be *this*.[[Reader]].
1. If *this*.[[Cancel]] is *true*, then:
1. Let _cancel_ be ? GetMethod(_reader_, `"cancel"`).
Copy link
Member

Choose a reason for hiding this comment

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

You should wrap this ? in a promise too.

index.bs Outdated
</thead>
<tr>
<td>\[[Prototype]]
<td>%AsyncIteratorPrototype%
Copy link
Member

Choose a reason for hiding this comment

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

Any reason why this isn't in a .non-normative?

Copy link
Collaborator

@MattiasBuelens MattiasBuelens left a comment

Choose a reason for hiding this comment

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

A few typos to fix. Also wondering whether we should use the Call helper function instead of Reflect.apply.

try {
const reader = this._reader;
const read = GetMethod(reader, 'read');
return Reflect.apply(read, reader, []);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we use the Call helper function here? The reference implementation doesn't seem to use Reflect anywhere else, although I'm not sure whether that's a design goal.

index.bs Outdated
1. Let _read_ be GetMethod(_reader_, `"read"`).
1. If _read_ is an abrupt completion, return <a>a promise rejected with</a> _read_.[[Value]].
1. Let _result_ be Call(_read_, _reader_).
1. If _result_ is an abrupt completion, return <a>a promise rejected with<a/> _result_.[[Value]].
Copy link
Collaborator

Choose a reason for hiding this comment

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

Typo: change <a/> to </a>

index.bs Outdated
@@ -1283,6 +1283,9 @@ would look like
<a href="#default-reader-cancel">cancel</a>(reason)
<a href="#default-reader-read">read</a>()
<a href="#default-reader-release-lock">releaseLock</a>()

<a href="#default-reader-getiterator">getIterator</a>({ preventClose = false })
Copy link
Collaborator

Choose a reason for hiding this comment

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

Typo: change preventClose to preventCancel. Same thing for the next line.

index.bs Outdated

<emu-alg>
1. If Type(_argument_) is not Object, return *false*.
1. If _argument_ does not have a [[Reader]] and [[PreventCancel]] internal slots, return *false*.
Copy link
Collaborator

Choose a reason for hiding this comment

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

@TimothyGu previously reported this, but it hasn't been tackled yet:

The "a" seems misplaced.

@ricea
Copy link
Collaborator

ricea commented Aug 29, 2018

I don't understand why a reader object is needed in order to iterate over a stream. I was hoping to be able to write

for await (const chunk of stream) { ... }

and have it do what I mean.

@domenic
Copy link
Member

domenic commented Aug 29, 2018

I can't remember why I thought using the reader as the entry point was a better idea than using the stream. Maybe explicitness or something? But yeah, probably we should switch it...

@devsnek
Copy link
Author

devsnek commented Aug 29, 2018

Does this work?

When @@asyncIterator of ReadableStream is called when _arguments_, do:

1. Let _getReader_ be ? GetMethod(*this*, `"getReader"`).
2. Let _reader_ be ? Call(_getReader_, *this*).
3. Let _getIterator_ be ? GetMethod(_reader_, @@asyncIterator).
4. Let _iter_ be ? Call(_getIterator_, _reader_, _arguments_).
5. Return _iter_.

@MattiasBuelens
Copy link
Collaborator

MattiasBuelens commented Aug 29, 2018

@devsnek That could work... assuming we only add readable[Symbol.asyncIterator](), and not readable.getIterator({ preventCancel }).

If we were to also add readable.getIterator({ preventCancel }) as a shorthand for readable.getReader().getIterator({ preventCancel }), then the stream may end up being locked to a reader with no way to unlock it. Consider:

const iterator = readable.getIterator({ preventCancel: true });
iterator.return();
// readable.locked === true;

Because return() does nothing when preventCancel === true, the stream stays locked to the iterator's reader.

What should happen in this case? We could call reader.releaseLock() from iterator.return(), except we might not be able to release the lock because there are still pending reads:

const iterator = readable.getIterator({ preventCancel: true });
const readPromise = iterator.next();
iterator.return(); // can't unlock, since read is still pending... what now?

I don't know if this is a case we want to support. Perhaps we don't need this, and we only have readable[Symbol.asyncIterator]() which will always either consume the whole stream or cancel it.

@devsnek
Copy link
Author

devsnek commented Aug 29, 2018

@MattiasBuelens i'm honestly not a fan of implicitly acquiring a reader for exactly those reasons.

One thing though -- This feature would depend on ReadableStreamDefaultBYOBReader having an @@asyncIterator, which it does not yet. I think it might be better to hold off on ReadableStream[@@asyncIterator] until byob reader has @@asyncIterator as well.

@ricea
Copy link
Collaborator

ricea commented Aug 30, 2018

@devsnek The default Reader for a ReadableByteStream is still a ReadableStreamDefaultReader, so we don't need to block on BYOB support. I can't see a way to do bring-your-own-buffer async iterators, as there's no way to supply a buffer to the iterator.


iterator.return(); // can't unlock, since read is still pending... what now?

Can we throw in this case? IIUC, it won't happen when using the for await (...) syntax, only when operating the iterator manually, so it doesn't matter if it's a bit clumsy.

Copy link
Member

@domenic domenic left a comment

Choose a reason for hiding this comment

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

First round of review. Here I address superficial issues. I'll upload a patch fixing all of these as I go, as penance for the long turnaround times so far.

index.bs Outdated
<p class="note">
The <code>getIterator</code> method returns an async iterator which can be used to consume the stream. The
{{ReadableStreamDefaultReaderAsyncIteratorPrototype/return()}} method of this iterator object will optionally
{{ReadableStreamDefaultReader/cancel()}} this reader.
Copy link
Member

Choose a reason for hiding this comment

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

We should talk about canceling the stream, not link to the public API on reader that does so.

We should also mention the auto-release behavior.

index.bs Outdated
1. If ! IsReadableStreamDefaultReader(*this*) is *false*, throw a *TypeError* exception.
1. If *this*.[[ownerReadableStream]] is *undefined*, throw a *TypeError* exception.
1. Let _iterator_ be ObjectCreate(`<a idl>ReadableStreamDefaultReaderAsyncIteratorPrototype</a>`, « [[Reader]],
[[PreventCancel]] »).
Copy link
Member

Choose a reason for hiding this comment

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

Interesting. Elsewhere in the spec we fail to add the internal slots. For consistency let's remove that here, and file a separate issue to fix it. (Although I'd like a fix that is less verbose and annoying than the ES spec's repetition of the internal slots list.)

index.bs Outdated
@@ -1283,6 +1283,9 @@ would look like
<a href="#default-reader-cancel">cancel</a>(reason)
<a href="#default-reader-read">read</a>()
<a href="#default-reader-release-lock">releaseLock</a>()

<a href="#default-reader-getiterator">getIterator</a>({ preventCancel = false })
Copy link
Member

Choose a reason for hiding this comment

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

In both the algorithm and the header, we should follow the pattern of pipeTo (and other web APIs), of not defaulting to false, but instead doing ToBoolean on the argument.

index.bs Outdated
1. Let _iterator_ be ObjectCreate(`<a idl>ReadableStreamDefaultReaderAsyncIteratorPrototype</a>`, « [[Reader]],
[[PreventCancel]] »).
1. Set _iterator_.[[Reader]] to *this*.
1. Set _iterator_.[[PreventCancel]] to ! ToBoolean(_cancel_).
Copy link
Member

Choose a reason for hiding this comment

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

cancel -> preventCancel

index.bs Outdated
<emu-alg>
1. If ! IsReadableStreamDefaultReader(*this*) is *false*, throw a *TypeError* exception.
1. If *this*.[[ownerReadableStream]] is *undefined*, throw a *TypeError* exception.
1. Let _iterator_ be ObjectCreate(`<a idl>ReadableStreamDefaultReaderAsyncIteratorPrototype</a>`, « [[Reader]],
Copy link
Member

Choose a reason for hiding this comment

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

ObjectCreate should not fail ,so add !

Copy link
Author

Choose a reason for hiding this comment

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

we don't use ! anywhere else.

index.bs Outdated
1. Let _reader_ be *this*.[[Reader]].
1. Let _read_ be GetMethod(_reader_, `"read"`).
1. If _read_ is an abrupt completion, return <a>a promise rejected with</a> _read_.[[Value]].
1. Let _result_ be Call(_read_, _reader_).
Copy link
Member

Choose a reason for hiding this comment

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

Missing .[[Value]]

index.bs Outdated
1. If _cancel_ is an abrupt completion, return <a>a promise rejected with</a> _cancel_.[[Value]].
1. Let _result_ be Call(_cancel_.[[Value]], _reader_, &laquo; _value_ &raquo;).
1. If _result_ is an abrupt completion, return <a>a promise rejected with</a> _result_.[[Value]].
1. Return <a>a promise resolved with</a> ? ReadableStreamCreateReadResult(_value_, *true*, *true*).
Copy link
Member

Choose a reason for hiding this comment

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

ReadableStreamCreateReadResult cannot fail, so use !

index.bs Outdated

<emu-alg>
1. If Type(_argument_) is not Object, return *false*.
1. If _argument_ does not have [[Reader]] and [[PreventCancel]] internal slots, return *false*.
Copy link
Member

Choose a reason for hiding this comment

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

We only generally test one slot.

index.bs Outdated
<td class="non-normative">%AsyncIteratorPrototype%
</tr>
<tr>
<td>\[[Reader]]
Copy link
Member

Choose a reason for hiding this comment

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

For no good reason, we use lowercase slot names in this spec. We have an open bug to fix, but we should align until then.

index.bs Outdated
<td class="non-normative">%AsyncIteratorPrototype%
</tr>
<tr>
<td>\[[Reader]]
Copy link
Member

Choose a reason for hiding this comment

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

We should have a slot name that's more distinguishable for the brand check.

index.bs Show resolved Hide resolved
@domenic
Copy link
Member

domenic commented Aug 30, 2018

OK, here are the big issues left, from what I can see:

  • Move/alias the methods to ReadableStream, instead of putting them on the default reader.
    • Should we alias, or move? I guess alias is nice since it lets you manually consume a couple with a given reader, then switch to for-await-of?
    • See the pattern used for cancel() for how this would work. TLDR don't call public API methods, use abstract ops
  • Handle the return-with-pending-reads case, by returning a rejected promise? This seems like it needs to be handled regardless of where we put the methods.
  • Should the methods of ReadableStreamDefaultReaderAsyncIteratorPrototype call the public API of the wrapped reader, or should they use the corresponding abstract ops directly?
    • I lean toward using the ops directly. Why add the complication of letting patches to ReadableStreamReader mess with how stuff works?
    • Then we could get rid of the "if this was an abrupt completion" checks, since e.g. ReadableStreamDefaultReaderRead is nothrow.

1. If _read_ is an abrupt completion, return <a>a promise rejected with</a> _read_.[[Value]].
1. Let _result_ be Call(_read_.[[Value]], _reader_).
1. If _result_ is an abrupt completion, return <a>a promise rejected with</a> _result_.[[Value]].
1. Return <a>a promise resolved with</a> _result_.[[Value]].
Copy link
Member

Choose a reason for hiding this comment

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

This is missing auto-release if result.[[Value]]'s done property is set to true, discussed in #778 (comment)

1. If _cancel_ is an abrupt completion, return <a>a promise rejected with</a> _cancel_.[[Value]].
1. Let _result_ be Call(_cancel_.[[Value]], _reader_, &laquo; _value_ &raquo;).
1. If _result_ is an abrupt completion, return <a>a promise rejected with</a> _result_.[[Value]].
1. Return <a>a promise resolved with</a> ! ReadableStreamCreateReadResult(_value_, *true*, *true*).
Copy link
Member

Choose a reason for hiding this comment

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

This is missing auto-release, discussed in #778 (comment) . It should happen in both branches.

@ricea
Copy link
Collaborator

ricea commented Aug 31, 2018

Should we alias, or move? I guess alias is nice since it lets you manually consume a couple with a given reader, then switch to for-await-of?

But then what would this do?

const reader = stream.getReader();
for await (const chunk of reader) {
  break;
}
reader.read();

Would the read() work? It would seem odd if the reader was unlocked behind the user's back.

If you want to use a reader first, and then iterate, it seems reasonable to wait for your read and then releaseLock(), ie.

const reader = stream.getReader();
const { value, done } = await reader.read();
// do something with first chunk
reader.releaseLock();
for await (const chunk of stream) {
  // do something with rest of the chunks
}

In general, I think having multiple ways to do something tends to lead to confused arguments about which way is "right". So I favour only having the methods on ReadableStream.

@devsnek
Copy link
Author

devsnek commented Aug 31, 2018

So I favour only having the methods on ReadableStream.

but we would still have to acquire and lock, it would just be behind the scenes. I'm okay with an alias but this should definitely be on the reader be sure that's where the data reading happens.

@domenic
Copy link
Member

domenic commented Aug 31, 2018

I'm okay with an alias but this should definitely be on the reader be sure that's where the data reading happens.

I'm not sure I agree with this reasoning. In general, we provide higher-level utilities on the stream, notably pipeTo() and tee(). Under the scenes, those use readers. But they're intentionally meant to allow consumers to not care about the whole reader paradigm.

In contrast, when you're using a reader, these high-level conveniences are not interesting or necessary---you're doing chunk-wise manual manipulation.

@devsnek devsnek closed this Sep 7, 2018
@devsnek devsnek deleted the feature/defaultreader-asynciterator branch September 7, 2018 18:33
@jimmywarting

This comment has been minimized.

@domenic

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

7 participants