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 Slot |
+ Description (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) {