diff --git a/index.bs b/index.bs index 8944441c0..e9acd02a7 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
@@ -399,11 +401,14 @@ like
     get locked()
 
     cancel(reason)
-    getReader()
+    getIterator({ preventCancel } = {})
+    getReader({ mode } = {})
     pipeThrough({ writable, readable },
                 { preventClose, preventAbort, preventCancel, signal } = {})
     pipeTo(dest, { preventClose, preventAbort, preventCancel, signal } = {})
     tee()
+
+    [@@asyncIterator]({ preventCancel } = {})
   }
 
@@ -602,6 +607,23 @@ option. If type is set to unde 1. Return ! ReadableStreamCancel(*this*, _reason_). +
getIterator({ preventCancel } = {})
+ +
+ The getIterator method returns an async iterator which can be used to consume the stream. The + {{ReadableStreamAsyncIteratorPrototype/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_. + +
getReader({ mode } = {})
@@ -792,6 +814,82 @@ option. If type is set to unde
+ +
[@@asyncIterator]({ preventCancel } = {})
+ +

+ The @@asyncIterator method is an alias of {{ReadableStream/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/getIterator()}} 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/getIterator()}}, 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 a *TypeError* exception. + 1. Let _reader_ be *this*.[[asyncIteratorReader]]. + 1. If _reader_.[[ownerReadableStream]] is *undefined*, return a promise rejected with a *TypeError* exception. + 1. Return the result of transforming ! ReadableStreamDefaultReaderRead(_reader_) with a fulfillment handler + which takes the argument _result_ and performs the following steps: + 1. Assert: Type(_result_) is Object. + 1. Let _value_ be ? Get(_result_, `"value"`). + 1. Let _done_ be ? Get(_result_, `"done"`). + 1. Assert: Type(_done_) is Boolean. + 1. If _done_ is *true*, perform ! ReadableStreamReaderGenericRelease(_reader_). + 1. Return ! ReadableStreamCreateReadResult(_value_, _done_, *true*). + + +

return( value )

+ + + 1. If ! IsReadableStreamAsyncIterator(*this*) is *false*, return a promise rejected with a *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 @@ -910,6 +1008,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 )

@@ -5985,6 +6092,7 @@ Forbes Lindesay, Forrest Norvell, Gary Blackwood, Gorgi Kosev, +Gus Caplan, 贺师俊 (hax), Isaac Schlueter, isonmad, diff --git a/reference-implementation/.eslintrc.json b/reference-implementation/.eslintrc.json index c09c1518a..f3486a456 100644 --- a/reference-implementation/.eslintrc.json +++ b/reference-implementation/.eslintrc.json @@ -160,7 +160,15 @@ "id-blacklist": "off", "id-length": "off", "id-match": "off", - "indent": ["error", 2, { "SwitchCase": 1 }], + "indent": ["error", 2, { + "SwitchCase": 1, + "FunctionDeclaration": { "parameters": "first" }, + "FunctionExpression": { "parameters": "first" }, + "CallExpression": { "arguments": "first" }, + "ArrayExpression": "first", + "ObjectExpression": "first", + "ImportDeclaration": "first" + }], "jsx-quotes": "off", "key-spacing": ["error", { "beforeColon": false, "afterColon": true, "mode": "strict" }], "keyword-spacing": ["error", { "before": true, "after": true }], diff --git a/reference-implementation/lib/readable-stream.js b/reference-implementation/lib/readable-stream.js index 6187fc40b..78be77a57 100644 --- a/reference-implementation/lib/readable-stream.js +++ b/reference-implementation/lib/readable-stream.js @@ -129,7 +129,7 @@ class ReadableStream { } if (IsWritableStream(dest) === false) { return Promise.reject( - new TypeError('ReadableStream.prototype.pipeTo\'s first argument must be a WritableStream')); + new TypeError('ReadableStream.prototype.pipeTo\'s first argument must be a WritableStream')); } preventClose = Boolean(preventClose); @@ -158,8 +158,72 @@ 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).then(result => { + assert(typeIsObject(result)); + const value = result.value; + const done = result.done; + assert(typeof done === 'boolean'); + if (done) { + ReadableStreamReaderGenericRelease(reader); + } + return ReadableStreamCreateReadResult(value, done, 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); +Object.defineProperty(ReadableStreamAsyncIteratorPrototype, 'next', { enumerable: false }); +Object.defineProperty(ReadableStreamAsyncIteratorPrototype, 'return', { enumerable: false }); + +Object.defineProperty(ReadableStream.prototype, Symbol.asyncIterator, { + value: ReadableStream.prototype.getIterator, + enumerable: false, + writable: true, + configurable: true +}); + module.exports = { CreateReadableByteStream, CreateReadableStream, @@ -194,7 +258,7 @@ function CreateReadableStream(startAlgorithm, pullAlgorithm, cancelAlgorithm, hi const controller = Object.create(ReadableStreamDefaultController.prototype); SetUpReadableStreamDefaultController( - stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark, sizeAlgorithm + stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark, sizeAlgorithm ); return stream; @@ -255,6 +319,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 ReadableStreamPipeTo(source, dest, preventClose, preventAbort, preventCancel, signal) { assert(IsReadableStream(source) === true); assert(IsWritableStream(dest) === true); @@ -420,10 +496,9 @@ function ReadableStreamPipeTo(source, dest, preventClose, preventAbort, preventC function doTheRest() { action().then( - () => finalize(originalIsError, originalError), - newError => finalize(true, newError) - ) - .catch(rethrowAssertionErrorRejection); + () => finalize(originalIsError, originalError), + newError => finalize(true, newError) + ).catch(rethrowAssertionErrorRejection); } } @@ -931,12 +1006,12 @@ function ReadableStreamReaderGenericRelease(reader) { if (reader._ownerReadableStream._state === 'readable') { defaultReaderClosedPromiseReject( - reader, - new TypeError('Reader was released and can no longer be used to monitor the stream\'s closedness')); + reader, + new TypeError('Reader was released and can no longer be used to monitor the stream\'s closedness')); } else { defaultReaderClosedPromiseResetToRejected( - reader, - new TypeError('Reader was released and can no longer be used to monitor the stream\'s closedness')); + reader, + new TypeError('Reader was released and can no longer be used to monitor the stream\'s closedness')); } reader._closedPromise.catch(() => {}); @@ -1098,8 +1173,7 @@ function ReadableStreamDefaultControllerCallPullIfNeeded(controller) { e => { ReadableStreamDefaultControllerError(controller, e); } - ) - .catch(rethrowAssertionErrorRejection); + ).catch(rethrowAssertionErrorRejection); return undefined; } @@ -1260,8 +1334,7 @@ function SetUpReadableStreamDefaultController( r => { ReadableStreamDefaultControllerError(controller, r); } - ) - .catch(rethrowAssertionErrorRejection); + ).catch(rethrowAssertionErrorRejection); } function SetUpReadableStreamDefaultControllerFromUnderlyingSource(stream, underlyingSource, highWaterMark, @@ -1533,8 +1606,7 @@ function ReadableByteStreamControllerCallPullIfNeeded(controller) { e => { ReadableByteStreamControllerError(controller, e); } - ) - .catch(rethrowAssertionErrorRejection); + ).catch(rethrowAssertionErrorRejection); return undefined; } @@ -1570,7 +1642,7 @@ function ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescripto assert(bytesFilled % elementSize === 0); return new pullIntoDescriptor.ctor( - pullIntoDescriptor.buffer, pullIntoDescriptor.byteOffset, bytesFilled / elementSize); + pullIntoDescriptor.buffer, pullIntoDescriptor.byteOffset, bytesFilled / elementSize); } function ReadableByteStreamControllerEnqueueChunkToQueue(controller, buffer, byteOffset, byteLength) { @@ -1994,19 +2066,18 @@ function SetUpReadableByteStreamController(stream, controller, startAlgorithm, p const startResult = startAlgorithm(); Promise.resolve(startResult).then( - () => { - controller._started = true; + () => { + controller._started = true; - assert(controller._pulling === false); - assert(controller._pullAgain === false); + assert(controller._pulling === false); + assert(controller._pullAgain === false); - ReadableByteStreamControllerCallPullIfNeeded(controller); - }, - r => { - ReadableByteStreamControllerError(controller, r); - } - ) - .catch(rethrowAssertionErrorRejection); + ReadableByteStreamControllerCallPullIfNeeded(controller); + }, + r => { + ReadableByteStreamControllerError(controller, r); + } + ).catch(rethrowAssertionErrorRejection); } function SetUpReadableByteStreamControllerFromUnderlyingSource(stream, underlyingByteSource, highWaterMark) { @@ -2063,6 +2134,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) { diff --git a/reference-implementation/lib/transform-stream.js b/reference-implementation/lib/transform-stream.js index 6c8608208..a5f14e90e 100644 --- a/reference-implementation/lib/transform-stream.js +++ b/reference-implementation/lib/transform-stream.js @@ -357,16 +357,15 @@ function TransformStreamDefaultSinkWriteAlgorithm(stream, chunk) { if (stream._backpressure === true) { const backpressureChangePromise = stream._backpressureChangePromise; assert(backpressureChangePromise !== undefined); - return backpressureChangePromise - .then(() => { - const writable = stream._writable; - const state = writable._state; - if (state === 'erroring') { - throw writable._storedError; - } - assert(state === 'writable'); - return TransformStreamDefaultControllerPerformTransform(controller, chunk); - }); + return backpressureChangePromise.then(() => { + const writable = stream._writable; + const state = writable._state; + if (state === 'erroring') { + throw writable._storedError; + } + assert(state === 'writable'); + return TransformStreamDefaultControllerPerformTransform(controller, chunk); + }); } return TransformStreamDefaultControllerPerformTransform(controller, chunk); diff --git a/reference-implementation/lib/writable-stream.js b/reference-implementation/lib/writable-stream.js index 661c4cf31..ab3dc5c52 100644 --- a/reference-implementation/lib/writable-stream.js +++ b/reference-implementation/lib/writable-stream.js @@ -272,14 +272,14 @@ function WritableStreamFinishErroring(stream) { const promise = stream._writableStreamController[AbortSteps](abortRequest._reason); promise.then( - () => { - abortRequest._resolve(); - WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream); - }, - reason => { - abortRequest._reject(reason); - WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream); - }); + () => { + abortRequest._resolve(); + WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream); + }, + reason => { + abortRequest._reject(reason); + WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream); + }); } function WritableStreamFinishInFlightWrite(stream) { @@ -769,18 +769,17 @@ function SetUpWritableStreamDefaultController(stream, controller, startAlgorithm const startResult = startAlgorithm(); const startPromise = Promise.resolve(startResult); startPromise.then( - () => { - assert(stream._state === 'writable' || stream._state === 'erroring'); - controller._started = true; - WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller); - }, - r => { - assert(stream._state === 'writable' || stream._state === 'erroring'); - controller._started = true; - WritableStreamDealWithRejection(stream, r); - } - ) - .catch(rethrowAssertionErrorRejection); + () => { + assert(stream._state === 'writable' || stream._state === 'erroring'); + controller._started = true; + WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller); + }, + r => { + assert(stream._state === 'writable' || stream._state === 'erroring'); + controller._started = true; + WritableStreamDealWithRejection(stream, r); + } + ).catch(rethrowAssertionErrorRejection); } function SetUpWritableStreamDefaultControllerFromUnderlyingSink(stream, underlyingSink, highWaterMark, sizeAlgorithm) { @@ -903,8 +902,7 @@ function WritableStreamDefaultControllerProcessClose(controller) { reason => { WritableStreamFinishInFlightCloseWithError(stream, reason); } - ) - .catch(rethrowAssertionErrorRejection); + ).catch(rethrowAssertionErrorRejection); } function WritableStreamDefaultControllerProcessWrite(controller, chunk) { @@ -935,8 +933,7 @@ function WritableStreamDefaultControllerProcessWrite(controller, chunk) { } WritableStreamFinishInFlightWriteWithError(stream, reason); } - ) - .catch(rethrowAssertionErrorRejection); + ).catch(rethrowAssertionErrorRejection); } function WritableStreamDefaultControllerGetBackpressure(controller) { diff --git a/reference-implementation/package.json b/reference-implementation/package.json index 19dd1e6a2..bda74d15a 100644 --- a/reference-implementation/package.json +++ b/reference-implementation/package.json @@ -14,7 +14,7 @@ "better-assert": "^1.0.2", "browserify": "^16.2.3", "debug": "^4.1.0", - "eslint": "^3.2.2", + "eslint": "^5.12.1", "minimatch": "^3.0.4", "nyc": "^13.0.1", "opener": "^1.5.1", diff --git a/reference-implementation/web-platform-tests b/reference-implementation/web-platform-tests index 4606e75ca..de6f8fcf9 160000 --- a/reference-implementation/web-platform-tests +++ b/reference-implementation/web-platform-tests @@ -1 +1 @@ -Subproject commit 4606e75ca8cd69830223f02e0fbd46fc160f431f +Subproject commit de6f8fcf9b87e80811e9267a886cf891f6f864e0