diff --git a/index.bs b/index.bs index 197615cb5..710d2290c 100644 --- a/index.bs +++ b/index.bs @@ -18,6 +18,8 @@ spec:promises-guide; type:dfn;
 urlPrefix: https://tc39.github.io/ecma262/; spec: ECMASCRIPT
     text: %Uint8Array%; url: #sec-typedarray-objects; type: constructor
+    text: %AsyncIteratorPrototype%; url: #sec-asynciteratorprototype; type: interface
+    text: AsyncIterator; url: #sec-asynciterator-interface; type: interface
     text: ArrayBuffer; url: #sec-arraybuffer-objects; type: interface
     text: DataView; url: #sec-dataview-objects; type: interface
     text: Number; url: #sec-ecmascript-language-types-number-type; type: interface
@@ -403,6 +405,8 @@ like
     pipeThrough({ writable, readable }, options)
     pipeTo(dest, { preventClose, preventAbort, preventCancel } = {})
     tee()
+
+    [@@asyncIterator]({ preventCancel } = {})
   }
 
@@ -866,6 +870,90 @@ option. If type is set to unde +
getIterator({ preventCancel = false } = {})
+ +
+ The getIterator method returns an async iterator which can be used to consume the stream. The + {{ReadableStreamDefaultReaderAsyncIteratorPrototype/return()}} method of this iterator object will, by default, + cancel the stream; it will also release the reader. +
+ + + 1. If ! IsReadableStream(*this*) is *false*, throw a *TypeError* exception. + 1. Let _reader_ be ? AcquireReadableStreamDefaultReader(*this*). + 1. Let _iterator_ be ! ObjectCreate(`ReadableStreamAsyncIteratorPrototype`). + 1. Set _iterator_.[[asyncIteratorReader]] to _reader_. + 1. Set _iterator_.[[preventCancel]] to ! ToBoolean(_preventCancel_). + 1. Return _iterator_. + + + +
[@@asyncIterator]()
+ +

+ The @@asyncIterator method is an alias of {{ReadableStreamDefaultReader/getIterator()}}. +

+ +The initial value of the @@asyncIterator method is the same function object as the initial value of the +{{ReadableStream/getIterator()}} method. + +

ReadableStreamAsyncIteratorPrototype

+ +{{ReadableStreamAsyncIteratorPrototype}} is an ordinary object that is used by {{ReadableStream/[@@asyncIterator]()}} to +construct the objects it returns. Instances of {{ReadableStreamAsyncIteratorPrototype}} implement the {{AsyncIterator}} +abstract interface from the JavaScript specification. [[!ECMASCRIPT]] + +The {{ReadableStreamAsyncIteratorPrototype}} object must have its \[[Prototype]] internal slot set to +{{%AsyncIteratorPrototype%}}. + +

Internal slots

+Objects created by {{ReadableStream/[@@asyncIterator]()}}, using {{ReadableStreamAsyncIteratorPrototype}} as their +prototype, are created with the internal slots described in the following table: + + + + + + + + + + + +
Internal SlotDescription (non-normative)
\[[asyncIteratorReader]] + A {{ReadableStreamDefaultReader}} instance +
\[[preventCancel]] + A boolean value indicating if the stream will be canceled when the async iterator's {{ReadableStreamAsyncIteratorPrototype/return()}} method is called +
+ +

next()

+ + + 1. If ! IsReadableStreamAsyncIterator(*this*) is *false*, return a promise rejected with *TypeError* exception. + 1. Let _reader_ be *this*.[[asyncIteratorReader]]. + 1. If _reader_.[[ownerReadableStream]] is *undefined*, return a promise rejected with a *TypeError* exception. + 1. Return ! ReadableStreamDefaultReaderRead(_reader_, *true*). + + +

return( value )

+ + + 1. If ! IsReadableStreamAsyncIterator(*this*) is *false*, return a promise rejected with *TypeError* exception. + 1. Let _reader_ be *this*.[[asyncIteratorReader]]. + 1. If _reader_.[[ownerReadableStream]] is *undefined*, return a promise rejected with a *TypeError* exception. + 1. If _reader_.[[readRequests]] is not empty, return a promise rejected with a *TypeError* exception. + 1. If *this*.[[preventCancel]] is *false*, then: + 1. Let _result_ be ! ReadableStreamReaderGenericCancel(_reader_, _value_). + 1. Perform ! ReadableStreamReaderGenericRelease(_reader_). + 1. Return the result of transforming _result_ by a fulfillment handler that returns ! + ReadableStreamCreateReadResult(_value_, *true*, *true*). + 1. Perform ! ReadableStreamReaderGenericRelease(_reader_). + 1. Return a promise resolved with ! ReadableStreamCreateReadResult(_value_, *true*, *true*). + +

General readable stream abstract operations

The following abstract operations, unlike most in this specification, are meant to be generally useful by other @@ -984,6 +1072,15 @@ readable stream is locked to a reader. 1. Return *true*. +

IsReadableStreamAsyncIterator ( x )

+ + + 1. If Type(_x_) is not Object, return *false*. + 1. If _x_ does not have a [[asyncIteratorReader]] internal slot, return *false*. + 1. Return *true*. + +

ReadableStreamTee ( stream, cloneForBranch2 )

diff --git a/reference-implementation/.eslintrc.json b/reference-implementation/.eslintrc.json index 42f6adb83..6b5123511 100644 --- a/reference-implementation/.eslintrc.json +++ b/reference-implementation/.eslintrc.json @@ -5,7 +5,7 @@ "es6": true }, "parserOptions": { - "ecmaVersion": 6 + "ecmaVersion": 2018 }, "globals": { "ReadableStream": false, diff --git a/reference-implementation/lib/readable-stream.js b/reference-implementation/lib/readable-stream.js index 435793d0a..22f90b584 100644 --- a/reference-implementation/lib/readable-stream.js +++ b/reference-implementation/lib/readable-stream.js @@ -267,8 +267,56 @@ class ReadableStream { const branches = ReadableStreamTee(this, false); return createArrayFromList(branches); } + + getIterator({ preventCancel = false } = {}) { + if (IsReadableStream(this) === false) { + throw streamBrandCheckException('getIterator'); + } + const reader = AcquireReadableStreamDefaultReader(this); + const iterator = Object.create(ReadableStreamAsyncIteratorPrototype); + iterator._asyncIteratorReader = reader; + iterator._preventCancel = Boolean(preventCancel); + return iterator; + } } +const AsyncIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf(async function* () {}).prototype); +const ReadableStreamAsyncIteratorPrototype = Object.setPrototypeOf({ + next() { + if (IsReadableStreamAsyncIterator(this) === false) { + return Promise.reject(streamAsyncIteratorBrandCheckException('next')); + } + const reader = this._asyncIteratorReader; + if (reader._ownerReadableStream === undefined) { + return Promise.reject(readerLockException('iterate')); + } + return ReadableStreamDefaultReaderRead(reader, true); + }, + + return(value) { + if (IsReadableStreamAsyncIterator(this) === false) { + return Promise.reject(streamAsyncIteratorBrandCheckException('next')); + } + const reader = this._asyncIteratorReader; + if (reader._ownerReadableStream === undefined) { + return Promise.reject(readerLockException('finish iterating')); + } + if (reader._readRequests.length > 0) { + return Promise.reject(new TypeError( + 'Tried to release a reader lock when that reader has pending read() calls un-settled')); + } + if (this._preventCancel === false) { + const result = ReadableStreamReaderGenericCancel(reader, value); + ReadableStreamReaderGenericRelease(reader); + return result.then(() => ReadableStreamCreateReadResult(value, true, true)); + } + ReadableStreamReaderGenericRelease(reader); + return Promise.resolve(ReadableStreamCreateReadResult(value, true, true)); + } +}, AsyncIteratorPrototype); + +ReadableStream.prototype[Symbol.asyncIterator] = ReadableStream.prototype.getIterator; + module.exports = { CreateReadableByteStream, CreateReadableStream, @@ -364,6 +412,18 @@ function IsReadableStreamLocked(stream) { return true; } +function IsReadableStreamAsyncIterator(x) { + if (!typeIsObject(x)) { + return false; + } + + if (!Object.prototype.hasOwnProperty.call(x, '_asyncIteratorReader')) { + return false; + } + + return true; +} + function ReadableStreamTee(stream, cloneForBranch2) { assert(IsReadableStream(stream) === true); assert(typeof cloneForBranch2 === 'boolean'); @@ -1956,6 +2016,10 @@ function streamBrandCheckException(name) { return new TypeError(`ReadableStream.prototype.${name} can only be used on a ReadableStream`); } +function streamAsyncIteratorBrandCheckException(name) { + return new TypeError(`ReadableStreamAsyncIterator.${name} can only be used on a ReadableSteamAsyncIterator`); +} + // Helper functions for the readers. function readerLockException(name) {