- From: Domenic Denicola <notifications@github.com>
- Date: Thu, 19 Nov 2020 13:56:55 -0800
- To: whatwg/streams <streams@noreply.github.com>
- Cc: Subscribed <subscribed@noreply.github.com>
- Message-ID: <whatwg/streams/pull/1083/review/534879849@github.com>
@domenic commented on this pull request.
> @@ -787,6 +795,42 @@ option. If {{UnderlyingSource/type}} is set to undefined (including via omission
|underlyingSource|, |underlyingSourceDict|, |highWaterMark|, |sizeAlgorithm|).
</div>
+<div algorithm>
+ The static <dfn id="rs-from" method for="ReadableStream">from(|asyncIterable|)</dfn> method steps
+ are:
+
+ 1. Let |stream| be undefined.
+ 1. Let |iteratorRecord| be ? [$GetIterator$](|asyncIterable|, `async`).
```suggestion
1. Let |iteratorRecord| be ? [$GetIterator$](|asyncIterable|, async).
```
Where the ES spec has special formatting or fonts, web specs ignore them all, generally. (Plus imposing our own formatting for strings.)
> + 1. Let |nextResult| be [$IteratorNext$](|iteratorRecord|).
+ 1. If |nextResult| is an abrupt completion, return [=a promise rejected with=]
+ |nextResult|.\[[Value]].
+ 1. Let |nextPromise| be [=a promise resolved with=] |nextResult|.\[[Value]].
+ 1. Return the result of [=reacting=] to |nextPromise| with the following fulfillment steps,
+ given |iterResult|:
+ 1. If [$Type$](|iterResult|) is not Object, throw a {{TypeError}}.
+ 1. Let |done| be ? [$IteratorComplete$](|iterResult|).
+ 1. If |done| is true:
+ 1. Perform ! [$ReadableStreamDefaultControllerClose$](stream.[=ReadableStream/[[controller]]=]).
+ 1. Otherwise:
+ 1. Let |value| be ? [$IteratorValue$](|iterResult|).
+ 1. Perform ! [$ReadableStreamDefaultControllerEnqueue$](stream.[=ReadableStream/[[controller]]=],
+ |value|).
+ 1. Let |cancelAlgorithm| be the following steps, given |reason|:
+ 1. Let |returnMethod| be [$GetMethod$](|iteratorRecord|.\[[Iterator]], "`return`").
Hmm, should we grab `return` before cancel time, or delay it as this PR does? The ES spec grabs the `next` method ahead of time... but I guess it usually delays the `"return"` method, so, yeah, this is correct as-is.
> + static from(asyncIterable) {
+ let stream;
+ const iteratorRecord = GetIterator(asyncIterable, 'async');
+
+ const startAlgorithm = () => undefined;
+
+ function pullAlgorithm() {
+ let nextResult;
+ try {
+ nextResult = IteratorNext(iteratorRecord);
+ } catch (e) {
+ return promiseRejectedWith(e);
+ }
+ const nextPromise = promiseResolvedWith(nextResult);
+ return transformPromiseWith(nextPromise, iterResult => {
+ if (typeof iterResult !== 'object') {
`null` should also throw. There might be a `typeIsObject` predicate in one of the helper files? Or maybe I killed it when we moved stuff to IDL and it became unnecessary.
(Tests would be ideal to catch this case.)
> @@ -34,3 +34,67 @@ exports.TransferArrayBuffer = O => {
exports.IsDetachedBuffer = O => {
return isFakeDetached in O;
};
+
+exports.Call = (F, V, args) => {
+ if (typeof F !== 'function') {
+ throw new TypeError('Argument is not a function');
+ }
+
+ return Function.prototype.apply.call(F, V, args);
Slightly nicer: `Reflect.apply(F, V, args)`
> + 1. Let |startAlgorithm| be the following steps:
+ 1. Set |iteratorRecord| to ? [$GetIterator$](|asyncIterable|, async).
+ 1. Let |pullAlgorithm| be the following steps:
+ 1. Let |nextResult| be [$IteratorNext$](|iteratorRecord|).
+ 1. If |nextResult| is an abrupt completion, return [=a promise rejected with=]
+ |nextResult|.\[[Value]].
+ 1. Let |nextPromise| be [=a promise resolved with=] |nextResult|.\[[Value]].
+ 1. Return the result of [=reacting=] to |nextPromise| with the following fulfillment steps,
+ given the argument |iterResult|:
+ 1. If [$Type$](|iterResult|) is not Object, throw a {{TypeError}}.
+ 1. Let |done| be ? [$IteratorComplete$](|iterResult|).
+ 1. If |done| is true:
+ 1. Perform ! [$ReadableStreamDefaultControllerClose$](stream.[=ReadableStream/[[controller]]=]).
+ 1. Otherwise:
+ 1. Let |value| be ? [$IteratorValue$](|iterResult|).
+ 1. Perform ! [$ReadableStreamDefaultControllerEnqueue$](stream.[=ReadableStream/[[controller]]=],
A future extension seems reasonable to me. Feel free to add a `<!-- comment -->` to the source file to remind us.
That said, it's hard enough to motivate implementers to add convenience features like async iteration in the first place; I worry that doing this in two pieces would reduce the chance of the second piece ever happening. Hmm.
> + given the argument |iterResult|:
+ 1. If [$Type$](|iterResult|) is not Object, throw a {{TypeError}}.
+ 1. Let |done| be ? [$IteratorComplete$](|iterResult|).
+ 1. If |done| is true:
+ 1. Perform ! [$ReadableStreamDefaultControllerClose$](stream.[=ReadableStream/[[controller]]=]).
+ 1. Otherwise:
+ 1. Let |value| be ? [$IteratorValue$](|iterResult|).
+ 1. Perform ! [$ReadableStreamDefaultControllerEnqueue$](stream.[=ReadableStream/[[controller]]=],
+ |value|).
+ 1. Let |cancelAlgorithm| be the following steps:
+ 1. Let |returnMethod| be ? [$GetMethod$](|iteratorRecord|.\[[Iterator]], "return").
+ 1. If |returnMethod| is undefined, return [=a promise resolved with=] undefined.
+ 1. Let |returnResult| be ? [$Call$](|returnMethod|, |iteratorRecord|.\[[Iterator]]).
+ 1. Return [=a promise resolved with=] |returnResult|.
+ 1. Set |stream| to ! [$CreateReadableStream$](|startAlgorithm|, |pullAlgorithm|, |cancelAlgorithm|).
+ 1. Return |stream|.
The reason I didn't catch this is because those references don't use `|var|` syntax, so they didn't get highlighted when I clicked step 1's "stream" variable.
> + }
+ if (typeof func !== 'function') {
+ throw new TypeError();
+ }
+ return func;
+};
+
+exports.GetIterator = (obj, hint = 'sync', method) => {
+ assert(hint === 'sync' || hint === 'async');
+ if (method === undefined) {
+ if (hint === 'async') {
+ method = exports.GetMethod(obj, Symbol.asyncIterator);
+ if (method === undefined) {
+ const syncMethod = exports.GetMethod(obj, Symbol.iterator);
+ const syncIterator = exports.GetIterator(obj, 'sync', syncMethod);
+ return syncIterator; // TODO sync to async iterator
> The ReadableStream.from() implementation already wraps all results from next() and return() in new promises, so we don't really need the sync-to-async adapter for that.
Hmm, interesting. Maybe it shouldn't? But, [`for-await-of`](https://tc39.es/ecma262/#sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset) does, indirectly, by using Await().
But what is the point of async-from-sync iterator then? I know I introduced it for a reason back when I wrote that spec...
I'll ask around.
> + 1. Let |nextResult| be [$IteratorNext$](|iteratorRecord|).
+ 1. If |nextResult| is an abrupt completion, return [=a promise rejected with=]
+ |nextResult|.\[[Value]].
+ 1. Let |nextPromise| be [=a promise resolved with=] |nextResult|.\[[Value]].
+ 1. Return the result of [=reacting=] to |nextPromise| with the following fulfillment steps,
+ given the argument |iterResult|:
+ 1. If [$Type$](|iterResult|) is not Object, throw a {{TypeError}}.
+ 1. Let |done| be ? [$IteratorComplete$](|iterResult|).
+ 1. If |done| is true:
+ 1. Perform ! [$ReadableStreamDefaultControllerClose$](stream.[=ReadableStream/[[controller]]=]).
+ 1. Otherwise:
+ 1. Let |value| be ? [$IteratorValue$](|iterResult|).
+ 1. Perform ! [$ReadableStreamDefaultControllerEnqueue$](stream.[=ReadableStream/[[controller]]=],
+ |value|).
+ 1. Let |cancelAlgorithm| be the following steps:
+ 1. Let |returnMethod| be [$GetMethod$](|iteratorRecord|.\[[Iterator]], "`return`").
Yeah, those seem largely designed for `for`-`await`-`of` consumers.
--
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/streams/pull/1083#pullrequestreview-534879849
Received on Thursday, 19 November 2020 21:57:08 UTC