From 13f2911f78cb84b147c27935cc3ef8751b1e6637 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 10 Nov 2020 21:47:39 +0100 Subject: [PATCH 01/22] Add ReadableStream.from(asyncIterable) --- index.bs | 40 ++++++++++++ .../lib/ReadableStream-impl.js | 47 +++++++++++++- .../lib/ReadableStream.webidl | 2 + .../lib/abstract-ops/ecmascript.js | 64 +++++++++++++++++++ 4 files changed, 152 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 10162f30c..6035da14d 100644 --- a/index.bs +++ b/index.bs @@ -45,6 +45,7 @@ urlPrefix: https://tc39.es/ecma262/; spec: ECMASCRIPT text: Number type; url: #sec-ecmascript-language-types-number-type text: Data Block; url: #sec-data-blocks type: abstract-op + text: Call; url: #sec-call text: CloneArrayBuffer; url: #sec-clonearraybuffer text: CopyDataBlockBytes; url: #sec-copydatablockbytes text: CreateArrayFromList; url: #sec-createarrayfromlist @@ -53,9 +54,14 @@ urlPrefix: https://tc39.es/ecma262/; spec: ECMASCRIPT text: Construct; url: #sec-construct text: DetachArrayBuffer; url: #sec-detacharraybuffer text: Get; url: #sec-get-o-p + text: GetIterator; url: #sec-getiterator + text: GetMethod; url: #sec-getmethod text: GetV; url: #sec-getv text: IsDetachedBuffer; url: #sec-isdetachedbuffer text: IsInteger; url: #sec-isinteger + text: IteratorComplete; url: #sec-iteratorcomplete + text: IteratorNext; url: #sec-iteratornext + text: IteratorValue; url: #sec-iteratorvalue text: OrdinaryObjectCreate; url: #sec-ordinaryobjectcreate text: SameValue; url: #sec-samevalue text: Type; url: #sec-ecmascript-data-types-and-values @@ -478,6 +484,8 @@ The Web IDL definition for the {{ReadableStream}} class is given as follows: interface ReadableStream { constructor(optional object underlyingSource, optional QueuingStrategy strategy = {}); + static ReadableStream from(any asyncIterable); + readonly attribute boolean locked; Promise cancel(optional any reason); @@ -808,6 +816,38 @@ option. If {{UnderlyingSource/type}} is set to undefined (including via omission |underlyingSource|, |underlyingSourceDict|, |highWaterMark|, |sizeAlgorithm|). +
+ The static from(|asyncIterable|) method steps + are: + + 1. Let |stream| be undefined. + 1. Let |iteratorRecord| be undefined. + 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]]=], + |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 locked getter steps are: diff --git a/reference-implementation/lib/ReadableStream-impl.js b/reference-implementation/lib/ReadableStream-impl.js index 1eda283e1..ee9765c8e 100644 --- a/reference-implementation/lib/ReadableStream-impl.js +++ b/reference-implementation/lib/ReadableStream-impl.js @@ -1,8 +1,10 @@ 'use strict'; const assert = require('assert'); +const { Call, GetMethod, GetIterator, IteratorNext, IteratorComplete, + IteratorValue } = require('./abstract-ops/ecmascript.js'); const { newPromise, resolvePromise, rejectPromise, promiseResolvedWith, promiseRejectedWith, - setPromiseIsHandledToTrue } = require('./helpers/webidl.js'); + setPromiseIsHandledToTrue, transformPromiseWith } = require('./helpers/webidl.js'); const { ExtractHighWaterMark, ExtractSizeAlgorithm } = require('./abstract-ops/queuing-strategy.js'); const aos = require('./abstract-ops/readable-streams.js'); const wsAOs = require('./abstract-ops/writable-streams.js'); @@ -151,6 +153,49 @@ exports.implementation = class ReadableStreamImpl { aos.ReadableStreamDefaultReaderRelease(reader); return promiseResolvedWith(undefined); } + + static from(asyncIterable) { + let stream; + let iteratorRecord; + + function startAlgorithm() { + iteratorRecord = GetIterator(asyncIterable, 'async'); + } + + 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') { + throw new TypeError(); + } + const done = IteratorComplete(iterResult); + if (done === true) { + aos.ReadableStreamDefaultControllerClose(stream._controller); + } else { + const value = IteratorValue(iterResult); + aos.ReadableStreamDefaultControllerEnqueue(stream._controller, value); + } + }); + } + + function cancelAlgorithm() { + const returnMethod = GetMethod(iteratorRecord.iterator, 'return'); + if (returnMethod === undefined) { + return promiseResolvedWith(undefined); + } + const returnResult = Call(returnMethod, iteratorRecord.iterator); + return promiseResolvedWith(returnResult); + } + + stream = aos.CreateReadableStream(startAlgorithm, pullAlgorithm, cancelAlgorithm); + return stream; + } }; // See pipeTo()/pipeThrough() for why this is needed. diff --git a/reference-implementation/lib/ReadableStream.webidl b/reference-implementation/lib/ReadableStream.webidl index af4a5ce47..f1158b2e0 100644 --- a/reference-implementation/lib/ReadableStream.webidl +++ b/reference-implementation/lib/ReadableStream.webidl @@ -2,6 +2,8 @@ interface ReadableStream { constructor(optional object underlyingSource, optional QueuingStrategy strategy = {}); + static ReadableStream from(any asyncIterable); + readonly attribute boolean locked; Promise cancel(optional any reason); diff --git a/reference-implementation/lib/abstract-ops/ecmascript.js b/reference-implementation/lib/abstract-ops/ecmascript.js index f2f3028b9..1a4375910 100644 --- a/reference-implementation/lib/abstract-ops/ecmascript.js +++ b/reference-implementation/lib/abstract-ops/ecmascript.js @@ -39,3 +39,67 @@ exports.CanTransferArrayBuffer = 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); +}; + +exports.GetMethod = (V, P) => { + const func = V[P]; + if (func === undefined || func === null) { + return undefined; + } + 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 + } + } else { + method = exports.GetMethod(obj, Symbol.iterator); + } + } + const iterator = exports.Call(method, obj); + if (typeof iterator !== 'object') { + throw new TypeError(); + } + const nextMethod = iterator.next; + return { iterator, nextMethod, done: false }; +}; + +exports.IteratorNext = (iteratorRecord, value) => { + let result; + if (value === undefined) { + result = exports.Call(iteratorRecord.nextMethod, iteratorRecord.iterator); + } else { + result = exports.Call(iteratorRecord.nextMethod, iteratorRecord.iterator, [value]); + } + if (typeof result !== 'object') { + throw new TypeError(); + } + return result; +}; + +exports.IteratorComplete = iterResult => { + assert(typeof iterResult === 'object'); + return Boolean(iterResult.done); +}; + +exports.IteratorValue = iterResult => { + assert(typeof iterResult === 'object'); + return iterResult.value; +}; From c94656763eda8a05dfb0c856e8146b284e90082b Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 10 Nov 2020 22:50:35 +0100 Subject: [PATCH 02/22] Format literals as code --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 6035da14d..16e87e225 100644 --- a/index.bs +++ b/index.bs @@ -823,7 +823,7 @@ option. If {{UnderlyingSource/type}} is set to undefined (including via omission 1. Let |stream| be undefined. 1. Let |iteratorRecord| be undefined. 1. Let |startAlgorithm| be the following steps: - 1. Set |iteratorRecord| to ? [$GetIterator$](|asyncIterable|, async). + 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=] @@ -840,7 +840,7 @@ option. If {{UnderlyingSource/type}} is set to undefined (including via omission 1. Perform ! [$ReadableStreamDefaultControllerEnqueue$](stream.[=ReadableStream/[[controller]]=], |value|). 1. Let |cancelAlgorithm| be the following steps: - 1. Let |returnMethod| be ? [$GetMethod$](|iteratorRecord|.\[[Iterator]], "return"). + 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|. From 9b72815bbf2c3483110139b1d6e7506c782d3712 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 10 Nov 2020 22:55:50 +0100 Subject: [PATCH 03/22] Handle sync errors from calling return() --- index.bs | 12 ++++++++---- .../lib/ReadableStream-impl.js | 14 ++++++++++++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/index.bs b/index.bs index 16e87e225..af341b4c5 100644 --- a/index.bs +++ b/index.bs @@ -840,10 +840,14 @@ option. If {{UnderlyingSource/type}} is set to undefined (including via omission 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. Let |returnMethod| be [$GetMethod$](|iteratorRecord|.\[[Iterator]], "`return`"). + 1. If |returnMethod| is an abrupt completion, return [=a promise rejected with=] + |returnMethod|.\[[Value]]. + 1. If |returnMethod|.\[[Value]] is undefined, return [=a promise resolved with=] undefined. + 1. Let |returnResult| be [$Call$](|returnMethod|.\[[Value]], |iteratorRecord|.\[[Iterator]]). + 1. If |returnResult| is an abrupt completion, return [=a promise rejected with=] + |returnResult|.\[[Value]]. + 1. Return [=a promise resolved with=] |returnResult|.\[[Value]]. 1. Set |stream| to ! [$CreateReadableStream$](|startAlgorithm|, |pullAlgorithm|, |cancelAlgorithm|). 1. Return |stream|.
diff --git a/reference-implementation/lib/ReadableStream-impl.js b/reference-implementation/lib/ReadableStream-impl.js index ee9765c8e..eb0b644c8 100644 --- a/reference-implementation/lib/ReadableStream-impl.js +++ b/reference-implementation/lib/ReadableStream-impl.js @@ -185,11 +185,21 @@ exports.implementation = class ReadableStreamImpl { } function cancelAlgorithm() { - const returnMethod = GetMethod(iteratorRecord.iterator, 'return'); + let returnMethod; + try { + returnMethod = GetMethod(iteratorRecord.iterator, 'return'); + } catch (e) { + return promiseRejectedWith(e); + } if (returnMethod === undefined) { return promiseResolvedWith(undefined); } - const returnResult = Call(returnMethod, iteratorRecord.iterator); + let returnResult; + try { + returnResult = Call(returnMethod, iteratorRecord.iterator); + } catch (e) { + return promiseRejectedWith(e); + } return promiseResolvedWith(returnResult); } From f931fc27c760c3b77d3769c3787874b6ff07aa82 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 10 Nov 2020 23:12:45 +0100 Subject: [PATCH 04/22] Move startAlgorithm steps to main steps --- index.bs | 5 ++--- reference-implementation/lib/ReadableStream-impl.js | 6 ++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/index.bs b/index.bs index af341b4c5..75356f653 100644 --- a/index.bs +++ b/index.bs @@ -821,9 +821,8 @@ option. If {{UnderlyingSource/type}} is set to undefined (including via omission are: 1. Let |stream| be undefined. - 1. Let |iteratorRecord| be undefined. - 1. Let |startAlgorithm| be the following steps: - 1. Set |iteratorRecord| to ? [$GetIterator$](|asyncIterable|, `async`). + 1. Let |iteratorRecord| be ? [$GetIterator$](|asyncIterable|, `async`). + 1. Let |startAlgorithm| be an algorithm that returns undefined. 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=] diff --git a/reference-implementation/lib/ReadableStream-impl.js b/reference-implementation/lib/ReadableStream-impl.js index eb0b644c8..dfd4f7b0e 100644 --- a/reference-implementation/lib/ReadableStream-impl.js +++ b/reference-implementation/lib/ReadableStream-impl.js @@ -156,11 +156,9 @@ exports.implementation = class ReadableStreamImpl { static from(asyncIterable) { let stream; - let iteratorRecord; + const iteratorRecord = GetIterator(asyncIterable, 'async'); - function startAlgorithm() { - iteratorRecord = GetIterator(asyncIterable, 'async'); - } + const startAlgorithm = () => undefined; function pullAlgorithm() { let nextResult; From 5c48420c221b6e07a3f3842cd04bbb55b24e233a Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 10 Nov 2020 23:22:16 +0100 Subject: [PATCH 05/22] Pass cancel reason to return() --- index.bs | 7 ++++--- reference-implementation/lib/ReadableStream-impl.js | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/index.bs b/index.bs index 75356f653..1965ee881 100644 --- a/index.bs +++ b/index.bs @@ -829,7 +829,7 @@ option. If {{UnderlyingSource/type}} is set to undefined (including via omission |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|: + given |iterResult|: 1. If [$Type$](|iterResult|) is not Object, throw a {{TypeError}}. 1. Let |done| be ? [$IteratorComplete$](|iterResult|). 1. If |done| is true: @@ -838,12 +838,13 @@ option. If {{UnderlyingSource/type}} is set to undefined (including via omission 1. Let |value| be ? [$IteratorValue$](|iterResult|). 1. Perform ! [$ReadableStreamDefaultControllerEnqueue$](stream.[=ReadableStream/[[controller]]=], |value|). - 1. Let |cancelAlgorithm| be the following steps: + 1. Let |cancelAlgorithm| be the following steps, given |reason|: 1. Let |returnMethod| be [$GetMethod$](|iteratorRecord|.\[[Iterator]], "`return`"). 1. If |returnMethod| is an abrupt completion, return [=a promise rejected with=] |returnMethod|.\[[Value]]. 1. If |returnMethod|.\[[Value]] is undefined, return [=a promise resolved with=] undefined. - 1. Let |returnResult| be [$Call$](|returnMethod|.\[[Value]], |iteratorRecord|.\[[Iterator]]). + 1. Let |returnResult| be [$Call$](|returnMethod|.\[[Value]], |iteratorRecord|.\[[Iterator]], + « |reason| »). 1. If |returnResult| is an abrupt completion, return [=a promise rejected with=] |returnResult|.\[[Value]]. 1. Return [=a promise resolved with=] |returnResult|.\[[Value]]. diff --git a/reference-implementation/lib/ReadableStream-impl.js b/reference-implementation/lib/ReadableStream-impl.js index dfd4f7b0e..a71d8bd70 100644 --- a/reference-implementation/lib/ReadableStream-impl.js +++ b/reference-implementation/lib/ReadableStream-impl.js @@ -182,7 +182,7 @@ exports.implementation = class ReadableStreamImpl { }); } - function cancelAlgorithm() { + function cancelAlgorithm(reason) { let returnMethod; try { returnMethod = GetMethod(iteratorRecord.iterator, 'return'); @@ -194,7 +194,7 @@ exports.implementation = class ReadableStreamImpl { } let returnResult; try { - returnResult = Call(returnMethod, iteratorRecord.iterator); + returnResult = Call(returnMethod, iteratorRecord.iterator, [reason]); } catch (e) { return promiseRejectedWith(e); } From 8e151d6d5a2537aa80c4dbc1312e264421a673bd Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 18 Nov 2020 00:38:13 +0100 Subject: [PATCH 06/22] Add ReadableStreamFromIterable abstract op --- index.bs | 67 ++++++++++--------- .../lib/ReadableStream-impl.js | 53 +-------------- .../lib/abstract-ops/readable-streams.js | 56 +++++++++++++++- 3 files changed, 93 insertions(+), 83 deletions(-) diff --git a/index.bs b/index.bs index 1965ee881..5fb6494ef 100644 --- a/index.bs +++ b/index.bs @@ -820,36 +820,7 @@ option. If {{UnderlyingSource/type}} is set to undefined (including via omission The static from(|asyncIterable|) method steps are: - 1. Let |stream| be undefined. - 1. Let |iteratorRecord| be ? [$GetIterator$](|asyncIterable|, `async`). - 1. Let |startAlgorithm| be an algorithm that returns undefined. - 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 |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`"). - 1. If |returnMethod| is an abrupt completion, return [=a promise rejected with=] - |returnMethod|.\[[Value]]. - 1. If |returnMethod|.\[[Value]] is undefined, return [=a promise resolved with=] undefined. - 1. Let |returnResult| be [$Call$](|returnMethod|.\[[Value]], |iteratorRecord|.\[[Iterator]], - « |reason| »). - 1. If |returnResult| is an abrupt completion, return [=a promise rejected with=] - |returnResult|.\[[Value]]. - 1. Return [=a promise resolved with=] |returnResult|.\[[Value]]. - 1. Set |stream| to ! [$CreateReadableStream$](|startAlgorithm|, |pullAlgorithm|, |cancelAlgorithm|). - 1. Return |stream|. + 1. Return ? [$ReadableStreamFromIterable$](|asyncIterable|).
@@ -2578,6 +2549,42 @@ create them does not matter. 1. Return « |branch1|, |branch2| ».
+
+ + ReadableStreamFromIterable(|asyncIterable|) performs the following steps: + + 1. Let |stream| be undefined. + 1. Let |iteratorRecord| be ? [$GetIterator$](|asyncIterable|, `async`). + 1. Let |startAlgorithm| be an algorithm that returns undefined. + 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 |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`"). + 1. If |returnMethod| is an abrupt completion, return [=a promise rejected with=] + |returnMethod|.\[[Value]]. + 1. If |returnMethod|.\[[Value]] is undefined, return [=a promise resolved with=] undefined. + 1. Let |returnResult| be [$Call$](|returnMethod|.\[[Value]], |iteratorRecord|.\[[Iterator]], + « |reason| »). + 1. If |returnResult| is an abrupt completion, return [=a promise rejected with=] + |returnResult|.\[[Value]]. + 1. Return [=a promise resolved with=] |returnResult|.\[[Value]]. + 1. Set |stream| to ! [$CreateReadableStream$](|startAlgorithm|, |pullAlgorithm|, |cancelAlgorithm|). + 1. Return |stream|. +
+

Interfacing with controllers

In terms of specification factoring, the way that the {{ReadableStream}} class encapsulates the diff --git a/reference-implementation/lib/ReadableStream-impl.js b/reference-implementation/lib/ReadableStream-impl.js index a71d8bd70..6c3b10f7d 100644 --- a/reference-implementation/lib/ReadableStream-impl.js +++ b/reference-implementation/lib/ReadableStream-impl.js @@ -1,10 +1,8 @@ 'use strict'; const assert = require('assert'); -const { Call, GetMethod, GetIterator, IteratorNext, IteratorComplete, - IteratorValue } = require('./abstract-ops/ecmascript.js'); const { newPromise, resolvePromise, rejectPromise, promiseResolvedWith, promiseRejectedWith, - setPromiseIsHandledToTrue, transformPromiseWith } = require('./helpers/webidl.js'); + setPromiseIsHandledToTrue } = require('./helpers/webidl.js'); const { ExtractHighWaterMark, ExtractSizeAlgorithm } = require('./abstract-ops/queuing-strategy.js'); const aos = require('./abstract-ops/readable-streams.js'); const wsAOs = require('./abstract-ops/writable-streams.js'); @@ -155,54 +153,7 @@ exports.implementation = class ReadableStreamImpl { } 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') { - throw new TypeError(); - } - const done = IteratorComplete(iterResult); - if (done === true) { - aos.ReadableStreamDefaultControllerClose(stream._controller); - } else { - const value = IteratorValue(iterResult); - aos.ReadableStreamDefaultControllerEnqueue(stream._controller, value); - } - }); - } - - function cancelAlgorithm(reason) { - let returnMethod; - try { - returnMethod = GetMethod(iteratorRecord.iterator, 'return'); - } catch (e) { - return promiseRejectedWith(e); - } - if (returnMethod === undefined) { - return promiseResolvedWith(undefined); - } - let returnResult; - try { - returnResult = Call(returnMethod, iteratorRecord.iterator, [reason]); - } catch (e) { - return promiseRejectedWith(e); - } - return promiseResolvedWith(returnResult); - } - - stream = aos.CreateReadableStream(startAlgorithm, pullAlgorithm, cancelAlgorithm); - return stream; + return aos.ReadableStreamFromIterable(asyncIterable); } }; diff --git a/reference-implementation/lib/abstract-ops/readable-streams.js b/reference-implementation/lib/abstract-ops/readable-streams.js index db1da4c73..7e317ce38 100644 --- a/reference-implementation/lib/abstract-ops/readable-streams.js +++ b/reference-implementation/lib/abstract-ops/readable-streams.js @@ -4,8 +4,8 @@ const assert = require('assert'); const { promiseResolvedWith, promiseRejectedWith, newPromise, resolvePromise, rejectPromise, uponPromise, setPromiseIsHandledToTrue, waitForAllPromise, transformPromiseWith, uponFulfillment, uponRejection } = require('../helpers/webidl.js'); -const { CanTransferArrayBuffer, CopyDataBlockBytes, CreateArrayFromList, IsDetachedBuffer, TransferArrayBuffer } = - require('./ecmascript.js'); +const { CanTransferArrayBuffer, Call, CopyDataBlockBytes, CreateArrayFromList, GetIterator, GetMethod, IsDetachedBuffer, + IteratorComplete, IteratorNext, IteratorValue, TransferArrayBuffer } = require('./ecmascript.js'); const { CloneAsUint8Array, IsNonNegativeNumber } = require('./miscellaneous.js'); const { EnqueueValueWithSize, ResetQueue } = require('./queue-with-sizes.js'); const { AcquireWritableStreamDefaultWriter, IsWritableStreamLocked, WritableStreamAbort, @@ -55,6 +55,7 @@ Object.assign(exports, { ReadableStreamDefaultControllerHasBackpressure, ReadableStreamDefaultReaderRead, ReadableStreamDefaultReaderRelease, + ReadableStreamFromIterable, ReadableStreamGetNumReadRequests, ReadableStreamHasDefaultReader, ReadableStreamPipeTo, @@ -1879,3 +1880,54 @@ function SetUpReadableByteStreamControllerFromUnderlyingSource( stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark, autoAllocateChunkSize ); } + +function ReadableStreamFromIterable(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') { + throw new TypeError(); + } + const done = IteratorComplete(iterResult); + if (done === true) { + ReadableStreamDefaultControllerClose(stream._controller); + } else { + const value = IteratorValue(iterResult); + ReadableStreamDefaultControllerEnqueue(stream._controller, value); + } + }); + } + + function cancelAlgorithm(reason) { + let returnMethod; + try { + returnMethod = GetMethod(iteratorRecord.iterator, 'return'); + } catch (e) { + return promiseRejectedWith(e); + } + if (returnMethod === undefined) { + return promiseResolvedWith(undefined); + } + let returnResult; + try { + returnResult = Call(returnMethod, iteratorRecord.iterator, [reason]); + } catch (e) { + return promiseRejectedWith(e); + } + return promiseResolvedWith(returnResult); + } + + stream = CreateReadableStream(startAlgorithm, pullAlgorithm, cancelAlgorithm); + return stream; +} From 0a8f260bd908251770c1eeb17ec4da02dab5bdc9 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 18 Nov 2020 00:42:56 +0100 Subject: [PATCH 07/22] Add ReadableStream.of(...chunks) --- index.bs | 7 +++++++ reference-implementation/lib/ReadableStream-impl.js | 4 ++++ reference-implementation/lib/ReadableStream.webidl | 1 + 3 files changed, 12 insertions(+) diff --git a/index.bs b/index.bs index 5fb6494ef..183a8574d 100644 --- a/index.bs +++ b/index.bs @@ -485,6 +485,7 @@ interface ReadableStream { constructor(optional object underlyingSource, optional QueuingStrategy strategy = {}); static ReadableStream from(any asyncIterable); + static ReadableStream of(any... chunks); readonly attribute boolean locked; @@ -823,6 +824,12 @@ option. If {{UnderlyingSource/type}} is set to undefined (including via omission 1. Return ? [$ReadableStreamFromIterable$](|asyncIterable|). +
+ The static of(...|chunks|) method steps are: + + 1. Return ? [$ReadableStreamFromIterable$](|chunks|). +
+
The locked getter steps are: diff --git a/reference-implementation/lib/ReadableStream-impl.js b/reference-implementation/lib/ReadableStream-impl.js index 6c3b10f7d..75650a846 100644 --- a/reference-implementation/lib/ReadableStream-impl.js +++ b/reference-implementation/lib/ReadableStream-impl.js @@ -155,6 +155,10 @@ exports.implementation = class ReadableStreamImpl { static from(asyncIterable) { return aos.ReadableStreamFromIterable(asyncIterable); } + + static of(...chunks) { + return aos.ReadableStreamFromIterable(chunks); + } }; // See pipeTo()/pipeThrough() for why this is needed. diff --git a/reference-implementation/lib/ReadableStream.webidl b/reference-implementation/lib/ReadableStream.webidl index f1158b2e0..16fc77c5a 100644 --- a/reference-implementation/lib/ReadableStream.webidl +++ b/reference-implementation/lib/ReadableStream.webidl @@ -3,6 +3,7 @@ interface ReadableStream { constructor(optional object underlyingSource, optional QueuingStrategy strategy = {}); static ReadableStream from(any asyncIterable); + static ReadableStream of(any... chunks); readonly attribute boolean locked; From d065c854a2d52592c8699597393788c3299df529 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 19 Nov 2020 23:28:28 +0100 Subject: [PATCH 08/22] Fix GetIterator call --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 183a8574d..c59dc66ea 100644 --- a/index.bs +++ b/index.bs @@ -2561,7 +2561,7 @@ create them does not matter. ReadableStreamFromIterable(|asyncIterable|) performs the following steps: 1. Let |stream| be undefined. - 1. Let |iteratorRecord| be ? [$GetIterator$](|asyncIterable|, `async`). + 1. Let |iteratorRecord| be ? [$GetIterator$](|asyncIterable|, async). 1. Let |startAlgorithm| be an algorithm that returns undefined. 1. Let |pullAlgorithm| be the following steps: 1. Let |nextResult| be [$IteratorNext$](|iteratorRecord|). From 991477e5d08465d51af11ae0a23c72918e6732a2 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 19 Nov 2020 23:29:04 +0100 Subject: [PATCH 09/22] Fix highlighting for |stream| --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index c59dc66ea..c66cd5fd0 100644 --- a/index.bs +++ b/index.bs @@ -2573,10 +2573,10 @@ create them does not matter. 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. Perform ! [$ReadableStreamDefaultControllerClose$](|stream|.[=ReadableStream/[[controller]]=]). 1. Otherwise: 1. Let |value| be ? [$IteratorValue$](|iterResult|). - 1. Perform ! [$ReadableStreamDefaultControllerEnqueue$](stream.[=ReadableStream/[[controller]]=], + 1. Perform ! [$ReadableStreamDefaultControllerEnqueue$](|stream|.[=ReadableStream/[[controller]]=], |value|). 1. Let |cancelAlgorithm| be the following steps, given |reason|: 1. Let |returnMethod| be [$GetMethod$](|iteratorRecord|.\[[Iterator]], "`return`"). From 0d07b3d7082e6b7f495bf7d835cb9c65bc9c588d Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 19 Nov 2020 23:30:35 +0100 Subject: [PATCH 10/22] Use Reflect.apply --- reference-implementation/lib/abstract-ops/ecmascript.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reference-implementation/lib/abstract-ops/ecmascript.js b/reference-implementation/lib/abstract-ops/ecmascript.js index 1a4375910..6531f5216 100644 --- a/reference-implementation/lib/abstract-ops/ecmascript.js +++ b/reference-implementation/lib/abstract-ops/ecmascript.js @@ -45,7 +45,7 @@ exports.Call = (F, V, args) => { throw new TypeError('Argument is not a function'); } - return Function.prototype.apply.call(F, V, args); + return Reflect.apply(F, V, args); }; exports.GetMethod = (V, P) => { From 99b858d9ba6c2366ba740df8c9b63fd705716ec6 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 19 Nov 2020 23:40:01 +0100 Subject: [PATCH 11/22] Fix "Type(x) is Object" checks --- .../lib/abstract-ops/ecmascript.js | 10 ++++++---- .../lib/abstract-ops/readable-streams.js | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/reference-implementation/lib/abstract-ops/ecmascript.js b/reference-implementation/lib/abstract-ops/ecmascript.js index 6531f5216..17f0ce532 100644 --- a/reference-implementation/lib/abstract-ops/ecmascript.js +++ b/reference-implementation/lib/abstract-ops/ecmascript.js @@ -3,6 +3,8 @@ const assert = require('assert'); const isFakeDetached = Symbol('is "detached" for our purposes'); +exports.typeIsObject = x => (typeof x === 'object' && x !== null) || typeof x === 'function'; + exports.CreateArrayFromList = elements => { // We use arrays to represent lists, so this is basically a no-op. // Do a slice though just in case we happen to depend on the unique-ness. @@ -74,7 +76,7 @@ exports.GetIterator = (obj, hint = 'sync', method) => { } } const iterator = exports.Call(method, obj); - if (typeof iterator !== 'object') { + if (!exports.typeIsObject(iterator)) { throw new TypeError(); } const nextMethod = iterator.next; @@ -88,18 +90,18 @@ exports.IteratorNext = (iteratorRecord, value) => { } else { result = exports.Call(iteratorRecord.nextMethod, iteratorRecord.iterator, [value]); } - if (typeof result !== 'object') { + if (!exports.typeIsObject(result)) { throw new TypeError(); } return result; }; exports.IteratorComplete = iterResult => { - assert(typeof iterResult === 'object'); + assert(exports.typeIsObject(iterResult)); return Boolean(iterResult.done); }; exports.IteratorValue = iterResult => { - assert(typeof iterResult === 'object'); + assert(exports.typeIsObject(iterResult)); return iterResult.value; }; diff --git a/reference-implementation/lib/abstract-ops/readable-streams.js b/reference-implementation/lib/abstract-ops/readable-streams.js index 7e317ce38..74352e9a0 100644 --- a/reference-implementation/lib/abstract-ops/readable-streams.js +++ b/reference-implementation/lib/abstract-ops/readable-streams.js @@ -5,7 +5,7 @@ const { promiseResolvedWith, promiseRejectedWith, newPromise, resolvePromise, re setPromiseIsHandledToTrue, waitForAllPromise, transformPromiseWith, uponFulfillment, uponRejection } = require('../helpers/webidl.js'); const { CanTransferArrayBuffer, Call, CopyDataBlockBytes, CreateArrayFromList, GetIterator, GetMethod, IsDetachedBuffer, - IteratorComplete, IteratorNext, IteratorValue, TransferArrayBuffer } = require('./ecmascript.js'); + IteratorComplete, IteratorNext, IteratorValue, TransferArrayBuffer, typeIsObject } = require('./ecmascript.js'); const { CloneAsUint8Array, IsNonNegativeNumber } = require('./miscellaneous.js'); const { EnqueueValueWithSize, ResetQueue } = require('./queue-with-sizes.js'); const { AcquireWritableStreamDefaultWriter, IsWritableStreamLocked, WritableStreamAbort, @@ -1896,7 +1896,7 @@ function ReadableStreamFromIterable(asyncIterable) { } const nextPromise = promiseResolvedWith(nextResult); return transformPromiseWith(nextPromise, iterResult => { - if (typeof iterResult !== 'object') { + if (!typeIsObject(iterResult)) { throw new TypeError(); } const done = IteratorComplete(iterResult); From 76080dad9197e5d2190e215e4432d00921f2a94f Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 19 Nov 2020 23:46:01 +0100 Subject: [PATCH 12/22] Add todo if we want to allow changing the queuing strategy --- index.bs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.bs b/index.bs index c66cd5fd0..695f50092 100644 --- a/index.bs +++ b/index.bs @@ -2578,6 +2578,8 @@ create them does not matter. 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`"). 1. If |returnMethod| is an abrupt completion, return [=a promise rejected with=] From ca0daf7c104fcb1cd33da43ec4aedf463ec981c9 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 19 Nov 2020 23:59:27 +0100 Subject: [PATCH 13/22] Revert "Add ReadableStream.of(...chunks)" --- index.bs | 7 ------- reference-implementation/lib/ReadableStream-impl.js | 4 ---- reference-implementation/lib/ReadableStream.webidl | 1 - 3 files changed, 12 deletions(-) diff --git a/index.bs b/index.bs index 695f50092..d7976762f 100644 --- a/index.bs +++ b/index.bs @@ -485,7 +485,6 @@ interface ReadableStream { constructor(optional object underlyingSource, optional QueuingStrategy strategy = {}); static ReadableStream from(any asyncIterable); - static ReadableStream of(any... chunks); readonly attribute boolean locked; @@ -824,12 +823,6 @@ option. If {{UnderlyingSource/type}} is set to undefined (including via omission 1. Return ? [$ReadableStreamFromIterable$](|asyncIterable|).
-
- The static of(...|chunks|) method steps are: - - 1. Return ? [$ReadableStreamFromIterable$](|chunks|). -
-
The locked getter steps are: diff --git a/reference-implementation/lib/ReadableStream-impl.js b/reference-implementation/lib/ReadableStream-impl.js index 75650a846..6c3b10f7d 100644 --- a/reference-implementation/lib/ReadableStream-impl.js +++ b/reference-implementation/lib/ReadableStream-impl.js @@ -155,10 +155,6 @@ exports.implementation = class ReadableStreamImpl { static from(asyncIterable) { return aos.ReadableStreamFromIterable(asyncIterable); } - - static of(...chunks) { - return aos.ReadableStreamFromIterable(chunks); - } }; // See pipeTo()/pipeThrough() for why this is needed. diff --git a/reference-implementation/lib/ReadableStream.webidl b/reference-implementation/lib/ReadableStream.webidl index 16fc77c5a..f1158b2e0 100644 --- a/reference-implementation/lib/ReadableStream.webidl +++ b/reference-implementation/lib/ReadableStream.webidl @@ -3,7 +3,6 @@ interface ReadableStream { constructor(optional object underlyingSource, optional QueuingStrategy strategy = {}); static ReadableStream from(any asyncIterable); - static ReadableStream of(any... chunks); readonly attribute boolean locked; From 0724c91fbcdea195309d9fe780317fe95d7402e4 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Sat, 26 Dec 2020 14:58:02 +0100 Subject: [PATCH 14/22] Fix Call abstract op --- reference-implementation/lib/abstract-ops/ecmascript.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reference-implementation/lib/abstract-ops/ecmascript.js b/reference-implementation/lib/abstract-ops/ecmascript.js index 17f0ce532..761c34d11 100644 --- a/reference-implementation/lib/abstract-ops/ecmascript.js +++ b/reference-implementation/lib/abstract-ops/ecmascript.js @@ -42,7 +42,7 @@ exports.IsDetachedBuffer = O => { return isFakeDetached in O; }; -exports.Call = (F, V, args) => { +exports.Call = (F, V, args = []) => { if (typeof F !== 'function') { throw new TypeError('Argument is not a function'); } From 523f3d094eb9428422dc11a5449e72df916cef07 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Sat, 26 Dec 2020 15:06:00 +0100 Subject: [PATCH 15/22] Implement CreateAsyncFromSyncIterator abstract op --- .../lib/abstract-ops/ecmascript.js | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/reference-implementation/lib/abstract-ops/ecmascript.js b/reference-implementation/lib/abstract-ops/ecmascript.js index 761c34d11..d97cd342b 100644 --- a/reference-implementation/lib/abstract-ops/ecmascript.js +++ b/reference-implementation/lib/abstract-ops/ecmascript.js @@ -61,6 +61,23 @@ exports.GetMethod = (V, P) => { return func; }; +exports.CreateAsyncFromSyncIterator = syncIteratorRecord => { + // Instead of re-implementing CreateAsyncFromSyncIterator and %AsyncFromSyncIteratorPrototype%, + // we use yield* inside an async generator function to achieve the same result. + + // Wrap the sync iterator inside a sync iterable, so we can use it with yield*. + const syncIterable = { + [Symbol.iterator]: () => syncIteratorRecord.iterator + }; + // Create an async generator function and immediately invoke it. + const asyncIterator = (async function* () { + return yield* syncIterable; + }()); + // Return as an async iterator record. + const nextMethod = asyncIterator.next; + return { iterator: asyncIterator, nextMethod, done: false }; +}; + exports.GetIterator = (obj, hint = 'sync', method) => { assert(hint === 'sync' || hint === 'async'); if (method === undefined) { @@ -68,8 +85,8 @@ exports.GetIterator = (obj, hint = 'sync', method) => { 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 + const syncIteratorRecord = exports.GetIterator(obj, 'sync', syncMethod); + return exports.CreateAsyncFromSyncIterator(syncIteratorRecord); } } else { method = exports.GetMethod(obj, Symbol.iterator); From 7b8db877cd8c663917b3ca6991a666a79aa118ca Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Sat, 26 Dec 2020 23:48:54 +0100 Subject: [PATCH 16/22] Set HWM to 0 for ReadableStream.from() --- reference-implementation/lib/abstract-ops/readable-streams.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reference-implementation/lib/abstract-ops/readable-streams.js b/reference-implementation/lib/abstract-ops/readable-streams.js index 74352e9a0..f68f13650 100644 --- a/reference-implementation/lib/abstract-ops/readable-streams.js +++ b/reference-implementation/lib/abstract-ops/readable-streams.js @@ -1928,6 +1928,6 @@ function ReadableStreamFromIterable(asyncIterable) { return promiseResolvedWith(returnResult); } - stream = CreateReadableStream(startAlgorithm, pullAlgorithm, cancelAlgorithm); + stream = CreateReadableStream(startAlgorithm, pullAlgorithm, cancelAlgorithm, 0); return stream; } From 078a33529da66107d6725ae618044c8baf94f38c Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 9 Feb 2021 00:23:58 +0100 Subject: [PATCH 17/22] Update spec text to set HWM to 0 --- index.bs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index d7976762f..97183e1b2 100644 --- a/index.bs +++ b/index.bs @@ -2583,7 +2583,8 @@ create them does not matter. 1. If |returnResult| is an abrupt completion, return [=a promise rejected with=] |returnResult|.\[[Value]]. 1. Return [=a promise resolved with=] |returnResult|.\[[Value]]. - 1. Set |stream| to ! [$CreateReadableStream$](|startAlgorithm|, |pullAlgorithm|, |cancelAlgorithm|). + 1. Set |stream| to ! [$CreateReadableStream$](|startAlgorithm|, |pullAlgorithm|, |cancelAlgorithm|, + 0). 1. Return |stream|.
From 3337ff771f1f700332c8d94a420281c67846a908 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 15 Apr 2021 23:15:21 +0200 Subject: [PATCH 18/22] Add error message --- reference-implementation/lib/abstract-ops/readable-streams.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reference-implementation/lib/abstract-ops/readable-streams.js b/reference-implementation/lib/abstract-ops/readable-streams.js index f68f13650..6effa5e6e 100644 --- a/reference-implementation/lib/abstract-ops/readable-streams.js +++ b/reference-implementation/lib/abstract-ops/readable-streams.js @@ -1897,7 +1897,7 @@ function ReadableStreamFromIterable(asyncIterable) { const nextPromise = promiseResolvedWith(nextResult); return transformPromiseWith(nextPromise, iterResult => { if (!typeIsObject(iterResult)) { - throw new TypeError(); + throw new TypeError('The iterator.next() method must return an object'); } const done = IteratorComplete(iterResult); if (done === true) { From 5275f3b625d4341d908cd8d4eecc05baaf8694d3 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 15 Apr 2021 23:26:37 +0200 Subject: [PATCH 19/22] Add more error messages --- reference-implementation/lib/abstract-ops/ecmascript.js | 6 +++--- .../lib/abstract-ops/readable-streams.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/reference-implementation/lib/abstract-ops/ecmascript.js b/reference-implementation/lib/abstract-ops/ecmascript.js index d97cd342b..731b38c0c 100644 --- a/reference-implementation/lib/abstract-ops/ecmascript.js +++ b/reference-implementation/lib/abstract-ops/ecmascript.js @@ -56,7 +56,7 @@ exports.GetMethod = (V, P) => { return undefined; } if (typeof func !== 'function') { - throw new TypeError(); + throw new TypeError(`${P} is not a function`); } return func; }; @@ -94,7 +94,7 @@ exports.GetIterator = (obj, hint = 'sync', method) => { } const iterator = exports.Call(method, obj); if (!exports.typeIsObject(iterator)) { - throw new TypeError(); + throw new TypeError('The iterator method must return an object'); } const nextMethod = iterator.next; return { iterator, nextMethod, done: false }; @@ -108,7 +108,7 @@ exports.IteratorNext = (iteratorRecord, value) => { result = exports.Call(iteratorRecord.nextMethod, iteratorRecord.iterator, [value]); } if (!exports.typeIsObject(result)) { - throw new TypeError(); + throw new TypeError('The iterator.next() method must return an object'); } return result; }; diff --git a/reference-implementation/lib/abstract-ops/readable-streams.js b/reference-implementation/lib/abstract-ops/readable-streams.js index 6effa5e6e..6c9631e09 100644 --- a/reference-implementation/lib/abstract-ops/readable-streams.js +++ b/reference-implementation/lib/abstract-ops/readable-streams.js @@ -1897,7 +1897,7 @@ function ReadableStreamFromIterable(asyncIterable) { const nextPromise = promiseResolvedWith(nextResult); return transformPromiseWith(nextPromise, iterResult => { if (!typeIsObject(iterResult)) { - throw new TypeError('The iterator.next() method must return an object'); + throw new TypeError('The promise returned by the iterator.next() method must fulfill with an object'); } const done = IteratorComplete(iterResult); if (done === true) { From 179698941e0dc9ab54aed9cebcf53f707003c498 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 4 Apr 2023 22:15:02 +0200 Subject: [PATCH 20/22] Throw if iterator.return() does not fulfill with an object --- index.bs | 12 ++++++++---- .../lib/abstract-ops/readable-streams.js | 13 ++++++++++--- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/index.bs b/index.bs index 97183e1b2..177eba057 100644 --- a/index.bs +++ b/index.bs @@ -2574,15 +2574,19 @@ create them does not matter. 1. Let |cancelAlgorithm| be the following steps, given |reason|: - 1. Let |returnMethod| be [$GetMethod$](|iteratorRecord|.\[[Iterator]], "`return`"). + 1. Let |iterator| be |iteratorRecord|.\[[Iterator]]. + 1. Let |returnMethod| be [$GetMethod$](|iterator|, "`return`"). 1. If |returnMethod| is an abrupt completion, return [=a promise rejected with=] |returnMethod|.\[[Value]]. 1. If |returnMethod|.\[[Value]] is undefined, return [=a promise resolved with=] undefined. - 1. Let |returnResult| be [$Call$](|returnMethod|.\[[Value]], |iteratorRecord|.\[[Iterator]], - « |reason| »). + 1. Let |returnResult| be [$Call$](|returnMethod|.\[[Value]], |iterator|, « |reason| »). 1. If |returnResult| is an abrupt completion, return [=a promise rejected with=] |returnResult|.\[[Value]]. - 1. Return [=a promise resolved with=] |returnResult|.\[[Value]]. + 1. Let |returnPromise| be [=a promise resolved with=] |returnResult|.\[[Value]]. + 1. Return the result of [=reacting=] to |returnPromise| with the following fulfillment steps, + given |iterResult|: + 1. If [$Type$](|iterResult|) is not Object, throw a {{TypeError}}. + 1. Return undefined. 1. Set |stream| to ! [$CreateReadableStream$](|startAlgorithm|, |pullAlgorithm|, |cancelAlgorithm|, 0). 1. Return |stream|. diff --git a/reference-implementation/lib/abstract-ops/readable-streams.js b/reference-implementation/lib/abstract-ops/readable-streams.js index 6c9631e09..285ac22e0 100644 --- a/reference-implementation/lib/abstract-ops/readable-streams.js +++ b/reference-implementation/lib/abstract-ops/readable-streams.js @@ -1910,9 +1910,10 @@ function ReadableStreamFromIterable(asyncIterable) { } function cancelAlgorithm(reason) { + const iterator = iteratorRecord.iterator; let returnMethod; try { - returnMethod = GetMethod(iteratorRecord.iterator, 'return'); + returnMethod = GetMethod(iterator, 'return'); } catch (e) { return promiseRejectedWith(e); } @@ -1921,11 +1922,17 @@ function ReadableStreamFromIterable(asyncIterable) { } let returnResult; try { - returnResult = Call(returnMethod, iteratorRecord.iterator, [reason]); + returnResult = Call(returnMethod, iterator, [reason]); } catch (e) { return promiseRejectedWith(e); } - return promiseResolvedWith(returnResult); + const returnPromise = promiseResolvedWith(returnResult); + return transformPromiseWith(returnPromise, iterResult => { + if (!typeIsObject(iterResult)) { + throw new TypeError('The promise returned by the iterator.return() method must fulfill with an object'); + } + return undefined; + }); } stream = CreateReadableStream(startAlgorithm, pullAlgorithm, cancelAlgorithm, 0); From d016390f6f8c1f1b0f5e5b3f317a5b7f87308aab Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 7 Jun 2023 18:33:05 +0200 Subject: [PATCH 21/22] Roll WPT --- reference-implementation/web-platform-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reference-implementation/web-platform-tests b/reference-implementation/web-platform-tests index 789685889..517e945bb 160000 --- a/reference-implementation/web-platform-tests +++ b/reference-implementation/web-platform-tests @@ -1 +1 @@ -Subproject commit 789685889a4bcc0898acac96960d060daf6c8e66 +Subproject commit 517e945bbfaf903f37a35c11700eb96662efbdd3 From bf5f3600ce21065d2da7f737d92d066ab09c1563 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 7 Jun 2023 18:38:23 +0200 Subject: [PATCH 22/22] Put abstract op in alphabetical order --- index.bs | 86 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/index.bs b/index.bs index 177eba057..764d152f5 100644 --- a/index.bs +++ b/index.bs @@ -2110,6 +2110,49 @@ The following abstract operations operate on {{ReadableStream}} instances at a h 1. Return true. +
+ + ReadableStreamFromIterable(|asyncIterable|) performs the following steps: + + 1. Let |stream| be undefined. + 1. Let |iteratorRecord| be ? [$GetIterator$](|asyncIterable|, async). + 1. Let |startAlgorithm| be an algorithm that returns undefined. + 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 |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 |iterator| be |iteratorRecord|.\[[Iterator]]. + 1. Let |returnMethod| be [$GetMethod$](|iterator|, "`return`"). + 1. If |returnMethod| is an abrupt completion, return [=a promise rejected with=] + |returnMethod|.\[[Value]]. + 1. If |returnMethod|.\[[Value]] is undefined, return [=a promise resolved with=] undefined. + 1. Let |returnResult| be [$Call$](|returnMethod|.\[[Value]], |iterator|, « |reason| »). + 1. If |returnResult| is an abrupt completion, return [=a promise rejected with=] + |returnResult|.\[[Value]]. + 1. Let |returnPromise| be [=a promise resolved with=] |returnResult|.\[[Value]]. + 1. Return the result of [=reacting=] to |returnPromise| with the following fulfillment steps, + given |iterResult|: + 1. If [$Type$](|iterResult|) is not Object, throw a {{TypeError}}. + 1. Return undefined. + 1. Set |stream| to ! [$CreateReadableStream$](|startAlgorithm|, |pullAlgorithm|, |cancelAlgorithm|, + 0). + 1. Return |stream|. +
+
ReadableStreamPipeTo(|source|, |dest|, |preventClose|, |preventAbort|, @@ -2549,49 +2592,6 @@ create them does not matter. 1. Return « |branch1|, |branch2| ».
-
- - ReadableStreamFromIterable(|asyncIterable|) performs the following steps: - - 1. Let |stream| be undefined. - 1. Let |iteratorRecord| be ? [$GetIterator$](|asyncIterable|, async). - 1. Let |startAlgorithm| be an algorithm that returns undefined. - 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 |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 |iterator| be |iteratorRecord|.\[[Iterator]]. - 1. Let |returnMethod| be [$GetMethod$](|iterator|, "`return`"). - 1. If |returnMethod| is an abrupt completion, return [=a promise rejected with=] - |returnMethod|.\[[Value]]. - 1. If |returnMethod|.\[[Value]] is undefined, return [=a promise resolved with=] undefined. - 1. Let |returnResult| be [$Call$](|returnMethod|.\[[Value]], |iterator|, « |reason| »). - 1. If |returnResult| is an abrupt completion, return [=a promise rejected with=] - |returnResult|.\[[Value]]. - 1. Let |returnPromise| be [=a promise resolved with=] |returnResult|.\[[Value]]. - 1. Return the result of [=reacting=] to |returnPromise| with the following fulfillment steps, - given |iterResult|: - 1. If [$Type$](|iterResult|) is not Object, throw a {{TypeError}}. - 1. Return undefined. - 1. Set |stream| to ! [$CreateReadableStream$](|startAlgorithm|, |pullAlgorithm|, |cancelAlgorithm|, - 0). - 1. Return |stream|. -
-

Interfacing with controllers

In terms of specification factoring, the way that the {{ReadableStream}} class encapsulates the