From 7dbcfedc5aef68760893e650b00421f192829f5d Mon Sep 17 00:00:00 2001 From: Domenic Denicola Date: Thu, 11 Jun 2020 11:42:23 -0400 Subject: [PATCH] Rewrite to use Web IDL, and generally modernize Closes #963. Normative changes to widely-implemented features, roughly in order of most disruptive to least-disruptive: * For the queuing strategy classes, their size and highWaterMark properties are now getters on the prototype, instead of data properties on the prototype and instance (respectively). Closes #1005. In particular this means that attempts to set either of them post-creation will throw a TypeError. Chromium already ships these semantics. * Functions which take a dictionary no longer accept non-objects. * For the queuing strategy classes, their highWaterMark property will no longer return a non-number from their highWaterMark properties, if one was passed to the constructor. Instead, NaN will be returned. * All methods and accessors are now enumerable, per Web IDL defaults, instead of non-enumerable, per ECMAScript defaults. * All classes are now exposed globally. Formerly, ReadableStreamDefaultReader, ReadableStreamBYOBReader, ReadableStreamDefaultController, ReadableByteStreamController, WritableStreamDefaultWriter, WritableStreamDefaultController, and TransformStreamDefaultController were not exposed. Closes #586. * All classes now have [Symbol.toStringTag] properties. Closes #952. * Some functions have changed their length property value. * Some exceptions are thrown earlier, at argument-conversion time. * Property lookup in options arguments now happens earlier, at argument-conversion time, and in alphabetical order, per dictionary rules. Normative changes to unimplemented features: * ReadableStream's getIterator() method has been renamed to values() as part of adopting Web IDL's infrastructure for async iterators. * The byobRequest property on ReadableByteStreamController now returns null when there is no BYOB request, instead of returning undefined. * The view property on ReadableStreamBYOBRequest now returns null when the view cannot be written into, instead of returning undefined. * Various byte-stream-related APIs that used to specifically prohibit detached buffers now check for zero-length views or buffers, which is a more general category. * The async iterator's next() and return() methods now behave more like async generators, e.g. returning promises fulfilled with { value: undefined, done: true } after return()ing the iterator, instead of returning a rejected promise. Editorial changes: * All APIs are specified to using Web IDL now, instead of using a modified version of the ECMAScript specification conventions. We continue using abstract operations and completion records for now, and we have to drop down to the ECMAScript level in a couple places (notably for dealing with %ObjectPrototype% vs. null-prototype iteration result objects, and transferring array buffers). But overall this removes a lot of type-checking and conversion boilerplate from the specification. Closes #963. Closes #1017. See #1036 for further followup on the iteration result objects. * Individual abstract operations, constructors, methods, and properties no longer have their own heading. They are instead lumped together in sections. Closes #885. * The constructors, methods, and properties are now documented in a per-class block, using the usual WHATWG "domintro" style. Closes #907. * Abstract operations are now consistently alphabetized within their section. Closes #684. * By using Bikeshed's
feature, we now get automatic identifier highlighting. Closes #687. * Switched to 100-character line limits, 1-space indents, and omitting end tags, per WHATWG conventions. * Removed usage of emu-algify in favor of using some more of Bikeshed's built-in features, plus manually annotating a few things. * Switched to concise Bikeshed linking syntax, e.g. [=term=] and [$AbstractOp$]. * Eliminated a number of utility abstract operations, especially around calling functions, by better using Web IDL. Other bug fixes: * Web IDL makes constructor behavior clear, so this closes #965. --- .editorconfig | 18 +- .gitignore | 3 - .pr-preview.json | 20 +- .travis.yml | 2 - Makefile | 18 +- README.md | 4 - index.bs | 10870 ++++++++-------- local-watch.js | 83 - package.json | 11 - reference-implementation/.eslintignore | 1 + reference-implementation/.eslintrc.json | 12 +- reference-implementation/.gitignore | 1 + reference-implementation/compile-idl.js | 22 + .../lib/ByteLengthQueuingStrategy-impl.js | 26 + .../lib/ByteLengthQueuingStrategy.webidl | 7 + .../lib/CountQueuingStrategy-impl.js | 26 + .../lib/CountQueuingStrategy.webidl | 7 + .../lib/QueuingStrategy.webidl | 6 + .../lib/QueuingStrategyInit.webidl | 3 + .../lib/ReadableByteStreamController-impl.js | 127 + .../lib/ReadableByteStreamController.webidl | 9 + .../lib/ReadableStream-impl.js | 180 + .../lib/ReadableStream.webidl | 38 + .../lib/ReadableStreamBYOBReader-impl.js | 53 + .../lib/ReadableStreamBYOBReader.webidl | 10 + .../lib/ReadableStreamBYOBRequest-impl.js | 41 + .../lib/ReadableStreamBYOBRequest.webidl | 7 + .../ReadableStreamDefaultController-impl.js | 60 + .../ReadableStreamDefaultController.webidl | 8 + .../lib/ReadableStreamDefaultReader-impl.js | 46 + .../lib/ReadableStreamDefaultReader.webidl | 10 + .../lib/TransformStream-impl.js | 48 + .../lib/TransformStream.webidl | 9 + .../TransformStreamDefaultController-impl.js | 23 + .../TransformStreamDefaultController.webidl | 8 + .../lib/Transformer.webidl | 11 + .../lib/UnderlyingSink.webidl | 12 + .../lib/UnderlyingSource.webidl | 15 + .../lib/WritableStream-impl.js | 56 + .../lib/WritableStream.webidl | 10 + .../WritableStreamDefaultController-impl.js | 29 + .../WritableStreamDefaultController.webidl | 4 + .../lib/WritableStreamDefaultWriter-impl.js | 73 + .../lib/WritableStreamDefaultWriter.webidl | 13 + .../lib/abstract-ops/ecmascript.js | 36 + .../lib/abstract-ops/internal-methods.js | 6 + .../lib/abstract-ops/miscellaneous.js | 17 + .../{ => abstract-ops}/queue-with-sizes.js | 8 +- .../lib/abstract-ops/queuing-strategy.js | 25 + .../readable-streams.js} | 1421 +- .../transform-streams.js} | 245 +- .../writable-streams.js} | 794 +- .../lib/byte-length-queuing-strategy.js | 12 - .../lib/count-queuing-strategy.js | 12 - reference-implementation/lib/helpers.js | 277 - .../{utils.js => helpers/miscellaneous.js} | 2 + .../lib/helpers/webidl.js | 162 + reference-implementation/lib/index.js | 28 +- reference-implementation/package.json | 16 +- .../run-web-platform-tests.js | 13 +- reference-implementation/web-platform-tests | 2 +- 61 files changed, 7419 insertions(+), 7697 deletions(-) delete mode 100644 local-watch.js delete mode 100644 package.json create mode 100644 reference-implementation/compile-idl.js create mode 100644 reference-implementation/lib/ByteLengthQueuingStrategy-impl.js create mode 100644 reference-implementation/lib/ByteLengthQueuingStrategy.webidl create mode 100644 reference-implementation/lib/CountQueuingStrategy-impl.js create mode 100644 reference-implementation/lib/CountQueuingStrategy.webidl create mode 100644 reference-implementation/lib/QueuingStrategy.webidl create mode 100644 reference-implementation/lib/QueuingStrategyInit.webidl create mode 100644 reference-implementation/lib/ReadableByteStreamController-impl.js create mode 100644 reference-implementation/lib/ReadableByteStreamController.webidl create mode 100644 reference-implementation/lib/ReadableStream-impl.js create mode 100644 reference-implementation/lib/ReadableStream.webidl create mode 100644 reference-implementation/lib/ReadableStreamBYOBReader-impl.js create mode 100644 reference-implementation/lib/ReadableStreamBYOBReader.webidl create mode 100644 reference-implementation/lib/ReadableStreamBYOBRequest-impl.js create mode 100644 reference-implementation/lib/ReadableStreamBYOBRequest.webidl create mode 100644 reference-implementation/lib/ReadableStreamDefaultController-impl.js create mode 100644 reference-implementation/lib/ReadableStreamDefaultController.webidl create mode 100644 reference-implementation/lib/ReadableStreamDefaultReader-impl.js create mode 100644 reference-implementation/lib/ReadableStreamDefaultReader.webidl create mode 100644 reference-implementation/lib/TransformStream-impl.js create mode 100644 reference-implementation/lib/TransformStream.webidl create mode 100644 reference-implementation/lib/TransformStreamDefaultController-impl.js create mode 100644 reference-implementation/lib/TransformStreamDefaultController.webidl create mode 100644 reference-implementation/lib/Transformer.webidl create mode 100644 reference-implementation/lib/UnderlyingSink.webidl create mode 100644 reference-implementation/lib/UnderlyingSource.webidl create mode 100644 reference-implementation/lib/WritableStream-impl.js create mode 100644 reference-implementation/lib/WritableStream.webidl create mode 100644 reference-implementation/lib/WritableStreamDefaultController-impl.js create mode 100644 reference-implementation/lib/WritableStreamDefaultController.webidl create mode 100644 reference-implementation/lib/WritableStreamDefaultWriter-impl.js create mode 100644 reference-implementation/lib/WritableStreamDefaultWriter.webidl create mode 100644 reference-implementation/lib/abstract-ops/ecmascript.js create mode 100644 reference-implementation/lib/abstract-ops/internal-methods.js create mode 100644 reference-implementation/lib/abstract-ops/miscellaneous.js rename reference-implementation/lib/{ => abstract-ops}/queue-with-sizes.js (83%) create mode 100644 reference-implementation/lib/abstract-ops/queuing-strategy.js rename reference-implementation/lib/{readable-stream.js => abstract-ops/readable-streams.js} (51%) rename reference-implementation/lib/{transform-stream.js => abstract-ops/transform-streams.js} (51%) rename reference-implementation/lib/{writable-stream.js => abstract-ops/writable-streams.js} (53%) delete mode 100644 reference-implementation/lib/byte-length-queuing-strategy.js delete mode 100644 reference-implementation/lib/count-queuing-strategy.js delete mode 100644 reference-implementation/lib/helpers.js rename reference-implementation/lib/{utils.js => helpers/miscellaneous.js} (84%) create mode 100644 reference-implementation/lib/helpers/webidl.js diff --git a/.editorconfig b/.editorconfig index 874a0b40a..47ebb2529 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,15 +7,19 @@ charset = utf-8 indent_size = 2 indent_style = space trim_trailing_whitespace = true +max_line_length = 100 -[*.{js,bs}] +[Makefile] +indent_style = tab + +[*.bs] +indent_size = 1 + +[*.py] +indent_size = 4 + +[*.js] max_line_length = 120 [.gitmodules] indent_style = tab - -[Makefile] -indent_style = tab - -[.travis.yml] -indent_size = 2 diff --git a/.gitignore b/.gitignore index a9e77d4d4..491ac7aa1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,3 @@ /deploy_key.pub /index.html /review.sh -/index.html.* -/node_modules/ -/npm-debug.log diff --git a/.pr-preview.json b/.pr-preview.json index 4b2477666..3b9efb150 100644 --- a/.pr-preview.json +++ b/.pr-preview.json @@ -1,15 +1,9 @@ { - "src_file": "index.bs", - "type": "bikeshed", - "params": { - "force": 1, - "md-status": "LS-PR", - "md-Text-Macro": "PR-NUMBER {{ pull_request.number }}" - }, - "post_processing": { - "name": "emu-algify", - "options": { - "throwingIndicators": true - } - } + "src_file": "index.bs", + "type": "bikeshed", + "params": { + "force": 1, + "md-status": "LS-PR", + "md-Text-Macro": "PR-NUMBER {{ pull_request.number }}" + } } diff --git a/.travis.yml b/.travis.yml index 9f4f47a56..a525d76bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: node_js node_js: stable -sudo: false env: global: @@ -12,7 +11,6 @@ before_install: script: - npm test - cd .. - - npm install - make deploy branches: diff --git a/Makefile b/Makefile index cf8a4e24a..60a5dd76a 100644 --- a/Makefile +++ b/Makefile @@ -2,17 +2,25 @@ SHELL=/bin/bash -o pipefail .PHONY: local remote deploy review remote: index.bs - curl https://api.csswg.org/bikeshed/ -f -F file=@index.bs > index.html.postbs -F md-Text-Macro="SNAPSHOT-LINK LOCAL COPY" - node_modules/.bin/emu-algify --throwing-indicators < index.html.postbs > index.html + @ (HTTP_STATUS=$$(curl https://api.csswg.org/bikeshed/ \ + --output index.html \ + --write-out "%{http_code}" \ + --header "Accept: text/plain, text/html" \ + -F die-on=warning \ + -F md-Text-Macro="COMMIT-SHA LOCAL COPY" \ + -F file=@index.bs) && \ + [[ "$$HTTP_STATUS" -eq "200" ]]) || ( \ + echo ""; cat index.html; echo ""; \ + rm -f index.html; \ + exit 22 \ + ); local: index.bs - bikeshed spec index.bs index.html.postbs --md-Text-Macro="SNAPSHOT-LINK LOCAL COPY" - node_modules/.bin/emu-algify --throwing-indicators < index.html.postbs > index.html + bikeshed spec index.bs index.html --md-Text-Macro="COMMIT-SHA LOCAL COPY" deploy: index.bs curl --remote-name --fail https://resources.whatwg.org/build/deploy.sh EXTRA_FILES="demos/* demos/**/*" \ - POST_BUILD_STEP='node_modules/.bin/emu-algify --throwing-indicators < "$$DIR/index.html" > "$$DIR/index.html.tmp"; mv "$$DIR/index.html.tmp" "$$DIR/index.html"' \ bash ./deploy.sh review: index.bs diff --git a/README.md b/README.md index 6cb27f4a5..56b3577b6 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,6 @@ implementation in order to pass those tests. ## Building "locally" -This standard requires a recent version of [Node.js](https://nodejs.org/en/) to be installed as a -prerequisite. Once that's done, you'll need to do a one-time run of `npm install` to set up our -tooling. - For quick local iteration, run `make`. To verify your changes locally, run `make deploy`. See more in the [WHATWG Contributor Guidelines](https://github.com/whatwg/meta/blob/master/CONTRIBUTING.md#building). diff --git a/index.bs b/index.bs index d83fcd895..c0ec6af8a 100644 --- a/index.bs +++ b/index.bs @@ -7,49 +7,60 @@ Abstract: This specification provides APIs for creating, composing, and consumin Abstract: to low-level I/O primitives. Translation: ja https://triple-underscore.github.io/Streams-ja.html !Demos: streams.spec.whatwg.org/demos -Opaque Elements: emu-alg +Indent: 1 +Markup Shorthands: markdown yes
-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
-    text: Uint8Array; url: #sec-typedarray-objects; type: interface
-    text: typed array; url: #sec-typedarray-objects; type: dfn
-    text: the typed array constructors table; url: #table-49; type: dfn
-    text: TypeError; url: #sec-native-error-types-used-in-this-standard-typeerror; type: exception
-    text: Invoke; url: #sec-invoke; type: abstract-op
-    text: DestructuringAssignmentEvaluation; url: #sec-runtime-semantics-destructuringassignmentevaluation; type: abstract-op
-    text: map; url: #sec-array.prototype.map; type: method; for: Array.prototype
-    text: CreateIterResultObject; url: #sec-createiterresultobject; type: abstract-op
+urlPrefix: https://tc39.es/ecma262/; spec: ECMASCRIPT
+ type: constructor
+  text: %Uint8Array%; url: #sec-typedarray-objects
+  text: %DataView%; url: #sec-dataview-constructor
+  text: %ArrayBuffer%; url: #sec-arraybuffer-constructor
+ type: interface
+  text: %ObjectPrototype%; url: #sec-properties-of-the-object-prototype-object
+  text: ArrayBuffer; url: #sec-arraybuffer-objects
+  text: DataView; url: #sec-dataview-objects
+  text: Number; url: #sec-ecmascript-language-types-number-type
+  text: Uint8Array; url: #sec-typedarray-objects
+ type: dfn
+  text: abstract operation; url: #sec-algorithm-conventions-abstract-operations
+  text: completion record; url: #sec-completion-record-specification-type
+  text: internal slot; url: #sec-object-internal-methods-and-internal-slots
+  text: record; url: #sec-list-and-record-specification-type
+  text: the current Realm; url: #current-realm
+  text: the typed array constructors table; url: #table-49
+  text: typed array; url: #sec-typedarray-objects
+  text: Number type; url: #sec-ecmascript-language-types-number-type
+ type: abstract-op
+  text: CloneArrayBuffer; url: #sec-clonearraybuffer
+  text: CopyDataBlockBytes; url: #sec-copydatablockbytes
+  text: CreateArrayFromList; url: #sec-createarrayfromlist
+  text: CreateBuiltinFunction; url: #sec-createbuiltinfunction
+  text: CreateDataProperty; url: #sec-createdataproperty
+  text: CreateIterResultObject; url: #sec-createiterresultobject
+  text: Construct; url: #sec-construct
+  text: DetachArrayBuffer; url: #sec-detacharraybuffer
+  text: Get; url: #sec-get
+  text: GetV; url: #sec-getv
+  text: IsDetachedBuffer; url: #sec-isdetachedbuffer
+  text: IsInteger; url: #sec-isinteger
+  text: OrdinaryObjectCreate; url: #sec-ordinaryobjectcreate
+  text: SetFunctionLength; url: #sec-setfunctionlength
+  text: SetFunctionName; url: #sec-setfunctionname
+  text: Type; url: #sec-ecmascript-data-types-and-values
+ text: TypeError; url: #sec-native-error-types-used-in-this-standard-typeerror; type: exception
+ text: map; url: #sec-array.prototype.map; type: method; for: Array.prototype
 

Introduction

@@ -58,5398 +69,5608 @@ urlPrefix: https://tc39.github.io/ecma262/; spec: ECMASCRIPT This section is non-normative. -Large swathes of the web platform are built on streaming data: that is, data that is created, processed, and consumed -in an incremental fashion, without ever reading all of it into memory. The Streams Standard provides a common set of -APIs for creating and interfacing with such streaming data, embodied in readable streams, -writable streams, and transform streams. - -These APIs have been designed to efficiently map to low-level I/O primitives, including specializations for byte streams -where appropriate. They allow easy composition of multiple streams into pipe chains, or can be used directly via -readers and writers. Finally, they are designed to automatically provide backpressure and queuing. - -This standard provides the base stream primitives which other parts of the web platform can use to expose their -streaming data. For example, [[FETCH]] exposes {{Response}} bodies as {{ReadableStream}} instances. More generally, the -platform is full of streaming abstractions waiting to be expressed as streams: multimedia streams, file streams, -inter-global communication, and more benefit from being able to process data incrementally instead of buffering it all -into memory and processing it in one go. By providing the foundation for these streams to be exposed to developers, the -Streams Standard enables use cases like: - - - -Web developers can also use the APIs described here to create their own streams, with the same APIs as those provided by -the platform. Other developers can then transparently compose platform-provided streams with those supplied by -libraries. In this way, the APIs described here provide unifying abstraction for all streams, encouraging an -ecosystem to grow around these shared and composable interfaces. +Large swathes of the web platform are built on streaming data: that is, data that is created, +processed, and consumed in an incremental fashion, without ever reading all of it into memory. The +Streams Standard provides a common set of APIs for creating and interfacing with such streaming +data, embodied in [=readable streams=], [=writable streams=], and [=transform streams=]. + +These APIs have been designed to efficiently map to low-level I/O primitives, including +specializations for byte streams where appropriate. They allow easy composition of multiple streams +into [=pipe chains=], or can be used directly via [=/readers=] and [=writers=]. Finally, they are +designed to automatically provide [=backpressure=] and queuing. + +This standard provides the base stream primitives which other parts of the web platform can use to +expose their streaming data. For example, [[FETCH]] exposes {{Response}} bodies as +{{ReadableStream}} instances. More generally, the platform is full of streaming abstractions waiting +to be expressed as streams: multimedia streams, file streams, inter-global communication, and more +benefit from being able to process data incrementally instead of buffering it all into memory and +processing it in one go. By providing the foundation for these streams to be exposed to developers, +the Streams Standard enables use cases like: + +* Video effects: piping a readable video stream through a transform stream that applies effects in + real time. +* Decompression: piping a file stream through a transform stream that selectively decompresses files + from a .tgz archive, turning them into <{img}> elements as the user scrolls through an + image gallery. +* Image decoding: piping an HTTP response stream through a transform stream that decodes bytes into + bitmap data, and then through another transform that translates bitmaps into PNGs. If installed + inside the {{ServiceWorkerGlobalScope/fetch}} hook of a service worker, this would allow + developers to transparently polyfill new image formats. [[SERVICE-WORKERS]] + +Web developers can also use the APIs described here to create their own streams, with the same APIs +as those provided by the platform. Other developers can then transparently compose platform-provided +streams with those supplied by libraries. In this way, the APIs described here provide unifying +abstraction for all streams, encouraging an ecosystem to grow around these shared and composable +interfaces.

Model

-A chunk is a single piece of data that is written to or read from a stream. It can be of any type; -streams can even contain chunks of different types. A chunk will often not be the most atomic unit of data for a given -stream; for example a byte stream might contain chunks consisting of 16 KiB {{Uint8Array}}s, instead of single bytes. +A chunk is a single piece of data that is written to or read from a stream. It can +be of any type; streams can even contain chunks of different types. A chunk will often not be the +most atomic unit of data for a given stream; for example a byte stream might contain chunks +consisting of 16 KiB {{Uint8Array}}s, instead of single bytes.

Readable streams

-A readable stream represents a source of data, from which you can read. In other words, data comes -out of a readable stream. Concretely, a readable stream is an instance of the {{ReadableStream}} class. +A readable stream represents a source of data, from which you can read. In other +words, data comes +out of a readable stream. Concretely, a readable stream is an instance of the +{{ReadableStream}} class. -Although a readable stream can be created with arbitrary behavior, most readable streams wrap a lower-level I/O source, -called the underlying source. There are two types of underlying source: push sources and pull sources. +Although a readable stream can be created with arbitrary behavior, most readable streams wrap a +lower-level I/O source, called the underlying source. There are two types of underlying +source: push sources and pull sources. -Push sources push data at you, whether or not you are listening for it. They may also -provide a mechanism for pausing and resuming the flow of data. An example push source is a TCP socket, where data is -constantly being pushed from the OS level, at a rate that can be controlled by changing the TCP window size. +Push sources push data at you, whether or not you are listening for it. +They may also provide a mechanism for pausing and resuming the flow of data. An example push source +is a TCP socket, where data is constantly being pushed from the OS level, at a rate that can be +controlled by changing the TCP window size. -Pull sources require you to request data from them. The data may be available -synchronously, e.g. if it is held by the operating system's in-memory buffers, or asynchronously, e.g. if it has to be -read from disk. An example pull source is a file handle, where you seek to specific locations and read specific amounts. +Pull sources require you to request data from them. The data may be +available synchronously, e.g. if it is held by the operating system's in-memory buffers, or +asynchronously, e.g. if it has to be read from disk. An example pull source is a file handle, where +you seek to specific locations and read specific amounts. -Readable streams are designed to wrap both types of sources behind a single, unified interface. For web -developer–created streams, the implementation details of a source are provided by an -object with certain methods and properties that is passed to the {{ReadableStream()}} constructor. +Readable streams are designed to wrap both types of sources behind a single, unified interface. For +web developer–created streams, the implementation details of a source are provided by an object with certain methods and properties that is passed to +the {{ReadableStream()}} constructor. -Chunks are enqueued into the stream by the stream's underlying source. They can then be read one at a -time via the stream's public interface, in particular by using a readable stream reader acquired using the -stream's {{ReadableStream/getReader()}} method. +[=Chunks=] are enqueued into the stream by the stream's [=underlying source=]. They can then be read +one at a time via the stream's public interface, in particular by using a [=readable stream reader=] +acquired using the stream's {{ReadableStream/getReader()}} method. Code that reads from a readable stream using its public interface is known as a consumer. -Consumers also have the ability to cancel a readable stream, using its -{{ReadableStream/cancel()}} method. This indicates that the consumer has lost interest in the stream, and will -immediately close the stream, throw away any queued chunks, and execute any cancellation mechanism of the -underlying source. +Consumers also have the ability to cancel a readable +stream, using its {{ReadableStream/cancel()}} method. This indicates that the consumer has lost +interest in the stream, and will immediately close the stream, throw away any queued [=chunks=], and +execute any cancellation mechanism of the [=underlying source=]. -Consumers can also tee a readable stream using its {{ReadableStream/tee()}} -method. This will lock the stream, making it no longer directly usable; however, it will -create two new streams, called branches, which can be consumed -independently. +Consumers can also tee a readable stream using its +{{ReadableStream/tee()}} method. This will [=locked to a reader|lock=] the stream, making it +no longer directly usable; however, it will create two new streams, called branches, which can be consumed independently. -For streams representing bytes, an extended version of the readable stream is provided to handle bytes -efficiently, in particular by minimizing copies. The underlying source for such a readable stream is called -an underlying byte source. A readable stream whose underlying source is an underlying byte source is -sometimes called a readable byte stream. Consumers of a readable byte stream can acquire a BYOB reader -using the stream's {{ReadableStream/getReader()}} method. +For streams representing bytes, an extended version of the [=readable stream=] is provided to handle +bytes efficiently, in particular by minimizing copies. The [=underlying source=] for such a readable +stream is called an underlying byte source. A readable stream whose underlying source is +an underlying byte source is sometimes called a readable byte stream. Consumers of a +readable byte stream can acquire a [=BYOB reader=] using the stream's {{ReadableStream/getReader()}} +method.

Writable streams

-A writable stream represents a destination for data, into which you can write. In other words, data -goes in to a writable stream. Concretely, a writable stream is an instance of the {{WritableStream}} class. +A writable stream represents a destination for data, into which you can write. In +other words, data goes in to a writable stream. Concretely, a writable stream is an +instance of the {{WritableStream}} class. Analogously to readable streams, most writable streams wrap a lower-level I/O sink, called the -underlying sink. Writable streams work to abstract away some of the complexity of the underlying sink, by -queuing subsequent writes and only delivering them to the underlying sink one by one. +underlying sink. Writable streams work to abstract away some of the complexity of the +underlying sink, by queuing subsequent writes and only delivering them to the underlying sink one by +one. -Chunks are written to the stream via its public interface, and are passed one at a time to the stream's -underlying sink. For web developer-created streams, the implementation details of the sink are provided by an object with certain methods that is passed to the {{WritableStream()}} constructor. +[=Chunks=] are written to the stream via its public interface, and are passed one at a time to the +stream's [=underlying sink=]. For web developer-created streams, the implementation details of the +sink are provided by an object with certain methods that is +passed to the {{WritableStream()}} constructor. -Code that writes into a writable stream using its public interface is known as a producer. +Code that writes into a writable stream using its public interface is known as a +producer. -Producers also have the ability to abort a writable stream, using its -{{WritableStream/abort()}} method. This indicates that the producer believes something has gone wrong, and that future -writes should be discontinued. It puts the stream in an errored state, even without a signal from the underlying -sink, and it discards all writes in the stream's internal queue. +Producers also have the ability to abort a writable stream, +using its {{WritableStream/abort()}} method. This indicates that the producer believes something has +gone wrong, and that future writes should be discontinued. It puts the stream in an errored state, +even without a signal from the [=underlying sink=], and it discards all writes in the stream's +[=internal queue=].

Transform streams

-A transform stream consists of a pair of streams: a writable stream, known as its writable side, and a readable stream, known as its readable side. In a manner -specific to the transform stream in question, writes to the writable side result in new data being made available for -reading from the readable side. - -Concretely, any object with a writable property and a readable property can serve as a -transform stream. However, the standard {{TransformStream}} class makes it much easier to create such a pair that is -properly entangled. It wraps a transformer, which defines algorithms for the specific transformation to be -performed. For web developer–created streams, the implementation details of a transformer are provided by an object with certain methods and properties that is passed to the {{TransformStream()}} constructor. - -An identity transform stream is a type of transform stream which forwards all chunks written -to its writable side to its readable side, without any changes. This can be useful in a variety of scenarios. By default, the {{TransformStream}} constructor will -create an identity transform stream, when no {{transformer/transform()}} method is present on the transformer -object. +A transform stream consists of a pair of streams: a [=writable stream=], known as +its writable side, and a [=readable stream=], known as its readable +side. In a manner specific to the transform stream in question, writes to the writable side +result in new data being made available for reading from the readable side. + +Concretely, any object with a writable property and a readable property +can serve as a transform stream. However, the standard {{TransformStream}} class makes it much +easier to create such a pair that is properly entangled. It wraps a transformer, which +defines algorithms for the specific transformation to be performed. For web developer–created +streams, the implementation details of a transformer are provided by an +object with certain methods and properties that is passed to the {{TransformStream()}} +constructor. + +An identity transform stream is a type of transform stream which forwards all +[=chunks=] written to its [=writable side=] to its [=readable side=], without any changes. This can +be useful in a variety of scenarios. By default, the +{{TransformStream}} constructor will create an identity transform stream, when no +{{Transformer/transform|transform()}} method is present on the [=transformer=] object. Some examples of potential transform streams include: - +* A GZIP compressor, to which uncompressed bytes are written and from which compressed bytes are + read; +* A video decoder, to which encoded bytes are written and from which uncompressed video frames are + read; +* A text decoder, to which bytes are written and from which strings are read; +* A CSV-to-JSON converter, to which strings representing lines of a CSV file are written and from + which corresponding JavaScript objects are read.

Pipe chains and backpressure

-Streams are primarily used by piping them to each other. A readable stream can be piped directly to a -writable stream, using its {{ReadableStream/pipeTo()}} method, or it can be piped through one or more transform streams -first, using its {{ReadableStream/pipeThrough()}} method. +Streams are primarily used by piping them to each other. A readable stream can be piped +directly to a writable stream, using its {{ReadableStream/pipeTo()}} method, or it can be piped +through one or more transform streams first, using its {{ReadableStream/pipeThrough()}} method. -A set of streams piped together in this way is referred to as a pipe chain. In a pipe chain, the -original source is the underlying source of the first readable stream in the chain; the -ultimate sink is the underlying sink of the final writable stream in the chain. +A set of streams piped together in this way is referred to as a pipe chain. In a pipe +chain, the original source is the [=underlying source=] of the first readable stream in +the chain; the ultimate sink is the [=underlying sink=] of the final writable stream in +the chain. -Once a pipe chain is constructed, it will propagate signals regarding how fast chunks should flow through it. If -any step in the chain cannot yet accept chunks, it propagates a signal backwards through the pipe chain, until -eventually the original source is told to stop producing chunks so fast. This process of normalizing flow from the -original source according to how fast the chain can process chunks is called backpressure. +Once a pipe chain is constructed, it will propagate signals regarding how fast [=chunks=] should +flow through it. If any step in the chain cannot yet accept chunks, it propagates a signal backwards +through the pipe chain, until eventually the original source is told to stop producing chunks so +fast. This process of normalizing flow from the original source according to how fast the chain can +process chunks is called backpressure. -Concretely, the original source is given the +Concretely, the [=original source=] is given the {{ReadableStreamDefaultController/desiredSize|controller.desiredSize}} (or -{{ReadableByteStreamController/desiredSize|byteController.desiredSize}}) value, and can then adjust its rate of data -flow accordingly. This value is derived from the {{WritableStreamDefaultWriter/desiredSize|writer.desiredSize}} -corresponding to the ultimate sink, which gets updated as the ultimate sink finishes writing chunks. The -{{ReadableStream/pipeTo()}} method used to construct the chain automatically ensures this information propagates back -through the pipe chain. - -When teeing a readable stream, the backpressure signals from its two -branches will aggregate, such that if neither branch is read from, a -backpressure signal will be sent to the underlying source of the original stream. - -Piping locks the readable and writable streams, preventing them from being manipulated for the duration of the -pipe operation. This allows the implementation to perform important optimizations, such as directly shuttling data from -the underlying source to the underlying sink while bypassing many of the intermediate queues. +{{ReadableByteStreamController/desiredSize|byteController.desiredSize}}) value, and can then adjust +its rate of data flow accordingly. This value is derived from the +{{WritableStreamDefaultWriter/desiredSize|writer.desiredSize}} corresponding to the [=ultimate +sink=], which gets updated as the ultimate sink finishes writing [=chunks=]. The +{{ReadableStream/pipeTo()}} method used to construct the chain automatically ensures this +information propagates back through the [=pipe chain=]. + +When [=tee a readable stream|teeing=] a readable stream, the [=backpressure=] signals from its two +[=branches of a readable stream tee|branches=] will aggregate, such that if neither branch is read +from, a backpressure signal will be sent to the [=underlying source=] of the original stream. + +Piping [=locks=] the readable and writable streams, preventing them from being manipulated for the +duration of the pipe operation. This allows the implementation to perform important optimizations, +such as directly shuttling data from the underlying source to the underlying sink while bypassing +many of the intermediate queues.

Internal queues and queuing strategies

-Both readable and writable streams maintain internal queues, which they use for similar purposes. In the -case of a readable stream, the internal queue contains chunks that have been enqueued by the underlying -source, but not yet read by the consumer. In the case of a writable stream, the internal queue contains -chunks which have been written to the stream by the producer, but not yet processed and acknowledged by the -underlying sink. - -A queuing strategy is an object that determines how a stream should signal backpressure based on -the state of its internal queue. The queuing strategy assigns a size to each chunk, and compares the -total size of all chunks in the queue to a specified number, known as the high water mark. The resulting -difference, high water mark minus total size, is used to determine the -desired size to fill the stream's queue. - -For readable streams, an underlying source can use this desired size as a backpressure signal, slowing down chunk -generation so as to try to keep the desired size above or at zero. For writable streams, a producer can behave -similarly, avoiding writes that would cause the desired size to go negative. - -Concretely, a queuing strategy for web developer–created streams is given by any JavaScript object -with a highWaterMark property. For byte streams the highWaterMark always has units of bytes. For other streams the default unit is chunks, but a -size() function can be included in the strategy object which returns the size -for a given chunk. This permits the highWaterMark to be specified in -arbitrary floating-point units. +Both readable and writable streams maintain internal queues, which they use for similar +purposes. In the case of a readable stream, the internal queue contains [=chunks=] that have been +enqueued by the [=underlying source=], but not yet read by the consumer. In the case of a writable +stream, the internal queue contains [=chunks=] which have been written to the stream by the +producer, but not yet processed and acknowledged by the [=underlying sink=]. + +A queuing strategy is an object that determines how a stream should signal +[=backpressure=] based on the state of its [=internal queue=]. The queuing strategy assigns a size +to each [=chunk=], and compares the total size of all chunks in the queue to a specified number, +known as the high water mark. The resulting difference, high water mark minus total size, +is used to determine the desired size to +fill the stream's queue. + +For readable streams, an underlying source can use this desired size as a backpressure signal, +slowing down chunk generation so as to try to keep the desired size above or at zero. For writable +streams, a producer can behave similarly, avoiding writes that would cause the desired size to go +negative. + +Concretely, a queuing strategy for web developer–created streams is given by +any JavaScript object with a {{QueuingStrategy/highWaterMark}} property. For byte streams the +{{QueuingStrategy/highWaterMark}} always has units of bytes. For other streams the default unit is +[=chunks=], but a {{QueuingStrategy/size|size()}} function can be included in the strategy object +which returns the size for a given chunk. This permits the {{QueuingStrategy/highWaterMark}} to be +specified in arbitrary floating-point units.
- A simple example of a queuing strategy would be one that assigns a size of one to each chunk, and has a high water - mark of three. This would mean that up to three chunks could be enqueued in a readable stream, or three chunks - written to a writable stream, before the streams are considered to be applying backpressure. - - In JavaScript, such a strategy could be written manually as { highWaterMark: 3, size() { return 1; }}, - or using the built-in {{CountQueuingStrategy}} class, as new CountQueuingStrategy({ highWaterMark: 3 }). + A simple example of a queuing strategy would be one that assigns a size of one to each chunk, and + has a high water mark of three. This would mean that up to three chunks could be enqueued in a + readable stream, or three chunks written to a writable stream, before the streams are considered to + be applying backpressure. + + In JavaScript, such a strategy could be written manually as { highWaterMark: + 3, size() { return 1; }}, or using the built-in {{CountQueuingStrategy}} class, as new CountQueuingStrategy({ highWaterMark: 3 }).

Locking

-A readable stream reader, or simply reader, is an object that allows -direct reading of chunks from a readable stream. Without a reader, a consumer can only perform -high-level operations on the readable stream: canceling the stream, or -piping the readable stream to a writable stream. A reader is acquired via the stream's -{{ReadableStream/getReader()}} method. +A readable stream reader, or simply reader, is an +object that allows direct reading of [=chunks=] from a [=readable stream=]. Without a reader, a +[=consumer=] can only perform high-level operations on the readable stream: [=cancel a readable +stream|canceling=] the stream, or [=piping=] the readable stream to a writable stream. A reader is +acquired via the stream's {{ReadableStream/getReader()}} method. + +A [=readable byte stream=] has the ability to vend two types of readers: default readers +and BYOB readers. BYOB ("bring your own buffer") readers allow reading into a +developer-supplied buffer, thus minimizing copies. A non-byte readable stream can only vend default +readers. Default readers are instances of the {{ReadableStreamDefaultReader}} class, while BYOB +readers are instances of {{ReadableStreamBYOBReader}}. + +Similarly, a writable stream writer, or simply +writer, is an object that allows direct writing of [=chunks=] to a [=writable stream=]. Without a +writer, a [=producer=] can only perform the high-level operations of [=abort a writable +stream|aborting=] the stream or [=piping=] a readable stream to the writable stream. Writers are +represented by the {{WritableStreamDefaultWriter}} class. + +

Under the covers, these high-level operations actually use a reader or writer +themselves.

+ +A given readable or writable stream only has at most one reader or writer at a time. We say in this +case the stream is locked, and that the +reader or writer is active. This state can be +determined using the {{ReadableStream/locked|readableStream.locked}} or +{{WritableStream/locked|writableStream.locked}} properties. + +A reader or writer also has the capability to release its lock, which makes it no longer active, and allows further readers or +writers to be acquired. This is done via the +{{ReadableStreamDefaultReader/releaseLock()|defaultReader.releaseLock()}}, +{{ReadableStreamBYOBReader/releaseLock()|byobReader.releaseLock()}}, or +{{WritableStreamDefaultWriter/releaseLock()|writer.releaseLock()}} method, as appropriate. + +

Conventions

-A readable byte stream has the ability to vend two types of readers: default readers and BYOB -readers. BYOB ("bring your own buffer") readers allow reading into a developer-supplied buffer, thus minimizing -copies. A non-byte readable stream can only vend default readers. Default readers are instances of the -{{ReadableStreamDefaultReader}} class, while BYOB readers are instances of {{ReadableStreamBYOBReader}}. +This specification depends on the Infra Standard. [[!INFRA]] -Similarly, a writable stream writer, or simply writer, is an object that -allows direct writing of chunks to a writable stream. Without a writer, a producer can only perform -the high-level operations of aborting the stream or piping a readable stream -to the writable stream. Writers are represented by the {{WritableStreamDefaultWriter}} class. +This specification uses the [=abstract operation=] concept from the JavaScript specification for its +internal algorithms. This includes treating their return values as [=completion records=], and the +use of ! and ? prefixes for unwrapping those completion records. [[!ECMASCRIPT]] -

Under the covers, these high-level operations actually use a reader or writer themselves.

+This specification also uses the [=internal slot=] and [=record=] concepts and notation from the +JavaScript specification. (Although, the internal slots are on Web IDL [=platform objects=] instead +of on JavaScript objects.) -A given readable or writable stream only has at most one reader or writer at a time. We say in this case the stream is -locked, and that the reader or writer is active. This state can be determined using the -{{ReadableStream/locked|readableStream.locked}} or {{WritableStream/locked|writableStream.locked}} properties. +

The reasons for the usage of these foreign JavaScript specification conventions are +largely historical. We urge you to avoid following our example when writing your own web +specifications. -A reader or writer also has the capability to release -its lock, which makes it no longer active, and allows further readers or writers to be acquired. This is done via -the {{ReadableStreamDefaultReader/releaseLock()|defaultReader.releaseLock()}}, -{{ReadableStreamBYOBReader/releaseLock()|byobReader.releaseLock()}}, or -{{WritableStreamDefaultWriter/releaseLock()|writer.releaseLock()}} method, as appropriate. +In this specification, all numbers are represented as double-precision 64-bit IEEE 754 floating +point values (like the JavaScript [=Number type=] or Web IDL {{unrestricted double}} type), and all +arithmetic operations performed on them must be done in the standard way for such values. This is +particularly important for the data structure described in [[#queue-with-sizes]]. [[!IEEE-754]]

Readable streams

Using readable streams

- The simplest way to consume a readable stream is to simply pipe it to a writable stream. - This ensures that backpressure is respected, and any errors (either writing or reading) are propagated through - the chain: - -

-    readableStream.pipeTo(writableStream)
-      .then(() => console.log("All data successfully written!"))
-      .catch(e => console.error("Something went wrong!", e));
-  
+ The simplest way to consume a readable stream is to simply [=piping|pipe=] it to a [=writable + stream=]. This ensures that [=backpressure=] is respected, and any errors (either writing or + reading) are propagated through the chain: + + + readableStream.pipeTo(writableStream) + .then(() => console.log("All data successfully written!")) + .catch(e => console.error("Something went wrong!", e)); +
- If you simply want to be alerted of each new chunk from a readable stream, you can pipe it to a - new writable stream that you custom-create for that purpose: - -

-    readableStream.pipeTo(new WritableStream({
-      write(chunk) {
-        console.log("Chunk received", chunk);
-      },
-      close() {
-        console.log("All data successfully read!");
-      },
-      abort(e) {
-        console.error("Something went wrong!", e);
-      }
-    }));
-  
- - By returning promises from your {{underlying sink/write()}} implementation, you can signal backpressure to the - readable stream. + If you simply want to be alerted of each new chunk from a readable stream, you can [=piping|pipe=] + it to a new [=writable stream=] that you custom-create for that purpose: + + + readableStream.pipeTo(new WritableStream({ + write(chunk) { + console.log("Chunk received", chunk); + }, + close() { + console.log("All data successfully read!"); + }, + abort(e) { + console.error("Something went wrong!", e); + } + })); + + + By returning promises from your {{UnderlyingSink/write|write()}} implementation, you can signal + [=backpressure=] to the readable stream.
- Although readable streams will usually be used by piping them to a writable stream, you can also read them directly by - acquiring a reader and using its read() method to get successive chunks. For example, this code - logs the next chunk in the stream, if available: + Although readable streams will usually be used by piping them to a writable stream, you can also + read them directly by acquiring a [=/reader=] and using its read() method to get + successive chunks. For example, this code logs the next [=chunk=] in the stream, if available: + + + const reader = readableStream.getReader(); + + reader.read().then( + ({ value, done }) => { + if (done) { + console.log("The stream was already closed!"); + } else { + console.log(value); + } + }, + e => console.error("The stream became errored and cannot be read from!", e) + ); + + + This more manual method of reading a stream is mainly useful for library authors building new + high-level operations on streams, beyond the provided ones of [=piping=] and [=tee a readable + stream|teeing=]. +
-

-    const reader = readableStream.getReader();
+
+ The above example showed using the readable stream's [=default reader=]. If the stream is a + [=readable byte stream=], you can also acquire a [=BYOB reader=] for it, which allows more + precise control over buffer allocation in order to avoid copies. For example, this code reads the + first 1024 bytes from the stream into a single memory buffer: + + + const reader = readableStream.getReader({ mode: "byob" }); + + let startingAB = new ArrayBuffer(1024); + readInto(startingAB) + .then(buffer => console.log("The first 1024 bytes:", buffer)) + .catch(e => console.error("Something went wrong!", e)); + + function readInto(buffer, offset = 0) { + if (offset === buffer.byteLength) { + return Promise.resolve(buffer); + } + + const view = new Uint8Array(buffer, offset, buffer.byteLength - offset); + return reader.read(view).then(newView => { + return readInto(newView.buffer, offset + newView.byteLength); + }); + } + + + An important thing to note here is that the final buffer value is different from the + startingAB, but it (and all intermediate buffers) shares the same backing memory + allocation. At each step, the buffer is transferred to a new + {{ArrayBuffer}} object. The newView is a new {{Uint8Array}}, with that {{ArrayBuffer}} + object as its buffer property, the offset that bytes were written to as its + byteOffset property, and the number of bytes that were written as its + byteLength property. +
- reader.read().then( - ({ value, done }) => { - if (done) { - console.log("The stream was already closed!"); - } else { - console.log(value); - } - }, - e => console.error("The stream became errored and cannot be read from!", e) - ); -
+

The {{ReadableStream}} class

- This more manual method of reading a stream is mainly useful for library authors building new high-level operations - on streams, beyond the provided ones of piping and teeing. - +The {{ReadableStream}} class is a concrete instance of the general [=readable stream=] concept. It +is adaptable to any [=chunk=] type, and maintains an internal queue to keep track of data supplied +by the [=underlying source=] but not yet read by any consumer. -
- The above example showed using the readable stream's default reader. If the stream is a readable byte - stream, you can also acquire a BYOB reader for it, which allows more precise control over buffer - allocation in order to avoid copies. For example, this code reads the first 1024 bytes from the stream into a single - memory buffer: - -

-    const reader = readableStream.getReader({ mode: "byob" });
-
-    let startingAB = new ArrayBuffer(1024);
-    readInto(startingAB)
-      .then(buffer => console.log("The first 1024 bytes:", buffer))
-      .catch(e => console.error("Something went wrong!", e));
-
-    function readInto(buffer, offset = 0) {
-      if (offset === buffer.byteLength) {
-        return Promise.resolve(buffer);
-      }
+

Interface definition

- const view = new Uint8Array(buffer, offset, buffer.byteLength - offset); - return reader.read(view).then(newView => { - return readInto(newView.buffer, offset + newView.byteLength); - }); - } -
+The Web IDL definition for the {{ReadableStream}} class is given as follows: - An important thing to note here is that the final buffer value is different from the - startingAB, but it (and all intermediate buffers) shares the same backing memory allocation. At each - step, the buffer is transferred to a new {{ArrayBuffer}} object. The - newView is a new {{Uint8Array}}, with that {{ArrayBuffer}} object as its buffer property, - the offset that bytes were written to as its byteOffset property, and the number of bytes that were - written as its byteLength property. -
+ +[Exposed=(Window,Worker,Worklet)] +interface ReadableStream { + constructor(optional object underlyingSource, optional QueuingStrategy strategy = {}); -<h3 id="rs-class" interface lt="ReadableStream">Class <code>ReadableStream</code></h3> + readonly attribute boolean locked; -The {{ReadableStream}} class is a concrete instance of the general <a>readable stream</a> concept. It is -adaptable to any <a>chunk</a> type, and maintains an internal queue to keep track of data supplied by the <a>underlying -source</a> but not yet read by any consumer. + Promise<void> cancel(optional any reason); + ReadableStreamReader getReader(optional ReadableStreamGetReaderOptions options = {}); + ReadableStream pipeThrough(ReadableWritablePair transform, optional StreamPipeOptions options = {}); + Promise<void> pipeTo(WritableStream destination, optional StreamPipeOptions options = {}); + sequence<ReadableStream> tee(); -<h4 id="rs-class-definition">Class definition</h4> + async iterable<any>(optional ReadableStreamIteratorOptions options = {}); +}; -<em>This section is non-normative.</em> +typedef (ReadableStreamDefaultReader or ReadableStreamBYOBReader) ReadableStreamReader; -If one were to write the {{ReadableStream}} class in something close to the syntax of [[!ECMASCRIPT]], it would look -like +enum ReadableStreamReaderMode { "byob" }; -<pre><code class="lang-javascript"> - class ReadableStream { - <a href="#rs-constructor">constructor</a>(<a href="#underlying-source-api">underlyingSource</a> = {}, <a href="#qs-api">strategy</a> = {}) +dictionary ReadableStreamGetReaderOptions { + ReadableStreamReaderMode mode; +}; - get <a href="#rs-locked">locked</a>() +dictionary ReadableStreamIteratorOptions { + boolean preventCancel = false; +}; - <a href="#rs-cancel">cancel</a>(reason) - <a href="#rs-get-iterator">getIterator</a>({ preventCancel } = {}) - <a href="#rs-get-reader">getReader</a>({ mode } = {}) - <a href="#rs-pipe-through">pipeThrough</a>({ writable, readable }, - { preventClose, preventAbort, preventCancel, signal } = {}) - <a href="#rs-pipe-to">pipeTo</a>(dest, { preventClose, preventAbort, preventCancel, signal } = {}) - <a href="#rs-tee">tee</a>() +dictionary ReadableWritablePair { + required ReadableStream readable; + required WritableStream writable; +}; - <a href="#rs-asynciterator">[@@asyncIterator]</a>({ preventCancel } = {}) - } -</code></pre> +dictionary StreamPipeOptions { + boolean preventClose = false; + boolean preventAbort = false; + boolean preventCancel = false; + AbortSignal signal; +}; +

Internal slots

-Instances of {{ReadableStream}} are created with the internal slots described in the following table: +Instances of {{ReadableStream}} are created with the internal slots described in the following +table: - - - - - - + - + - + - + - + - + +
Internal SlotDescription (non-normative)
\[[disturbed]] - A boolean flag set to true when the stream has been read from or - canceled -
Internal Slot + Description (non-normative) +
\[[readableStreamController]] - A {{ReadableStreamDefaultController}} or {{ReadableByteStreamController}} created with - the ability to control the state and queue of this stream; also used for the IsReadableStream brand check -
\[[disturbed]] + A boolean flag set to true when the stream has been read from or + canceled
\[[reader]] - A {{ReadableStreamDefaultReader}} or {{ReadableStreamBYOBReader}} instance, if the stream - is locked to a reader, or undefined if it is not -
\[[readableStreamController]] + A {{ReadableStreamDefaultController}} or + {{ReadableByteStreamController}} created with the ability to control the state and queue of this + stream
\[[state]] - A string containing the stream's current state, used internally; one of - "readable", "closed", or "errored" -
\[[reader]] + A {{ReadableStreamDefaultReader}} or {{ReadableStreamBYOBReader}} + instance, if the stream is [=locked to a reader=], or undefined if it is not
\[[storedError]] - A value indicating how the stream failed, to be given as a failure reason or exception - when trying to operate on an errored stream -
\[[state]] + A string containing the stream's current state, used internally; one + of "readable", "closed", or "errored" +
\[[storedError]] + A value indicating how the stream failed, to be given as a failure + reason or exception when trying to operate on an errored stream
-

new -ReadableStream(underlyingSource = {}, strategy = {})

+

The underlying source API

-
- The underlyingSource argument represents the underlying source, as described in - [[#underlying-source-api]]. - - The strategy argument represents the stream's queuing strategy, as described in [[#qs-api]]. If it - is not provided, the default behavior will be the same as a {{CountQueuingStrategy}} with a high water mark - of 1. -
- - - 1. Perform ! InitializeReadableStream(*this*). - 1. Let _size_ be ? GetV(_strategy_, `"size"`). - 1. Let _highWaterMark_ be ? GetV(_strategy_, `"highWaterMark"`). - 1. Let _type_ be ? GetV(_underlyingSource_, `"type"`). - 1. Let _typeString_ be ? ToString(_type_). - 1. If _typeString_ is `"bytes"`, - 1. If _size_ is not *undefined*, throw a *RangeError* exception. - 1. If _highWaterMark_ is *undefined*, let _highWaterMark_ be *0*. - 1. Set _highWaterMark_ to ? ValidateAndNormalizeHighWaterMark(_highWaterMark_). - 1. Perform ? SetUpReadableByteStreamControllerFromUnderlyingSource(*this*, _underlyingSource_, _highWaterMark_). - 1. Otherwise, if _type_ is *undefined*, - 1. Let _sizeAlgorithm_ be ? MakeSizeAlgorithmFromSizeFunction(_size_). - 1. If _highWaterMark_ is *undefined*, let _highWaterMark_ be *1*. - 1. Set _highWaterMark_ to ? ValidateAndNormalizeHighWaterMark(_highWaterMark_). - 1. Perform ? SetUpReadableStreamDefaultControllerFromUnderlyingSource(*this*, _underlyingSource_, _highWaterMark_, - _sizeAlgorithm_). - 1. Otherwise, throw a *RangeError* exception. - - -

Underlying source API

+The {{ReadableStream()}} constructor accepts as its first argument a JavaScript object representing +the [=underlying source=]. Such objects can contain any of the following properties: -
+ +dictionary UnderlyingSource { + UnderlyingSourceStartCallback start; + UnderlyingSourcePullCallback pull; + UnderlyingSourceCancelCallback cancel; + ReadableStreamType type; + [EnforceRange] unsigned long long autoAllocateChunkSize; +}; -<em>This section is non-normative.</em> +typedef (ReadableStreamDefaultController or ReadableByteStreamController) ReadableStreamController; -The {{ReadableStream()}} constructor accepts as its first argument a JavaScript object representing the <a>underlying -source</a>. Such objects can contain any of the following properties: +callback UnderlyingSourceStartCallback = any (ReadableStreamController controller); +callback UnderlyingSourcePullCallback = Promise<void> (ReadableStreamController controller); +callback UnderlyingSourceCancelCallback = Promise<void> (optional any reason); + +enum ReadableStreamType { "bytes" }; +
-
start(controller)
-
-

A function that is called immediately during creation of the {{ReadableStream}}.

+
start(controller)
+
+

A function that is called immediately during creation of the {{ReadableStream}}. + +

Typically this is used adapt a [=push source=] by setting up relevant event listeners, as in + the example of [[#example-rs-push-no-backpressure]], or to acquire access to a [=pull source=], + as in [[#example-rs-pull]]. + +

If this setup process is asynchronous, it can return a promise to signal success or failure; + a rejected promise will error the stream. Any thrown exceptions will be re-thrown by the + {{ReadableStream()}} constructor. + +

pull(controller)
+
+

A function that is called whenever the stream's [=internal queue=] of chunks becomes not full, + i.e. whenever the queue's [=desired size to fill a stream's internal queue|desired size=] becomes + positive. Generally, it will be called repeatedly until the queue reaches its [=high water mark=] + (i.e. until the desired size becomes + non-positive). + +

For [=push sources=], this can be used to resume a paused flow, as in + [[#example-rs-push-backpressure]]. For [=pull sources=], it is used to acquire new [=chunks=] to + enqueue into the stream, as in [[#example-rs-pull]]. + +

This function will not be called until {{UnderlyingSource/start|start()}} successfully + completes. Additionally, it will only be called repeatedly if it enqueues at least one chunk or + fulfills a BYOB request; a no-op {{UnderlyingSource/pull|pull()}} implementation will not be + continually called. + +

If the function returns a promise, then it will not be called again until that promise + fulfills. (If the promise rejects, the stream will become errored.) This is mainly used in the + case of pull sources, where the promise returned represents the process of acquiring a new chunk. + Throwing an exception is treated the same as returning a rejected promise. + +

cancel(reason)
+
+

A function that is called whenever the [=consumer=] [=cancel a readable stream|cancels=] the + stream, via {{ReadableStream/cancel()|stream.cancel()}}, + {{ReadableStreamDefaultReader/cancel()|defaultReader.cancel()}}, or + {{ReadableStreamBYOBReader/cancel()|byobReader.cancel()}}. It takes as its argument the same + value as was passed to those methods by the consumer. + +

Readable streams can additionally be canceled under certain conditions during [=piping=]; see + the definition of the {{ReadableStream/pipeTo()}} method for more details. + +

For all streams, this is generally used to release access to the underlying resource; see for + example [[#example-rs-push-no-backpressure]]. + +

If the shutdown process is asynchronous, it can return a promise to signal success or failure; + the result will be communicated via the return value of the cancel() + method that was called. Additionally, a rejected promise will error the stream, instead of + letting it close. Throwing an exception is treated the same as returning a rejected promise. + +

type (byte streams + only)
+
+

Can be set to "bytes" to signal that the + constructed {{ReadableStream}} is a readable byte stream. This ensures that the resulting + {{ReadableStream}} will successfully be able to vend [=BYOB readers=] via its + {{ReadableStream/getReader()}} method. It also affects the |controller| argument passed to the + {{UnderlyingSource/start|start()}} and {{UnderlyingSource/pull|pull()}} methods; see below. + +

For an example of how to set up a readable byte stream, including using the different + controller interface, see [[#example-rbs-push]]. + +

Setting any value other than "{{ReadableStreamType/bytes}}" or undefined will cause the + {{ReadableStream()}} constructor to throw an exception. + +

autoAllocateChunkSize (byte streams only)
+
+

Can be set to a positive integer to cause the implementation to automatically allocate buffers + for the underlying source code to write into. In this case, when a [=consumer=] is using a + [=default reader=], the stream implementation will automatically allocate an {{ArrayBuffer}} of + the given size, so that {{ReadableByteStreamController/byobRequest|controller.byobRequest}} is + always present, as if the consumer was using a [=BYOB reader=]. + +

This is generally used to cut down on the amount of code needed to handle consumers that use + default readers, as can be seen by comparing [[#example-rbs-push]] without auto-allocation to + [[#example-rbs-pull]] with auto-allocation. +

-

Typically this is used adapt a push source by setting up relevant event listeners, as in the example of - [[#example-rs-push-no-backpressure]], or to acquire access to a pull source, as in [[#example-rs-pull]].

+The type of the |controller| argument passed to the {{UnderlyingSource/start|start()}} and +{{UnderlyingSource/pull|pull()}} methods depends on the value of the {{UnderlyingSource/type}} +option. If {{UnderlyingSource/type}} is set to undefined (including via omission), then +|controller| will be a {{ReadableStreamDefaultController}}. If it's set to +"{{ReadableStreamType/bytes}}", then |controller| will be a {{ReadableByteStreamController}}. -

If this setup process is asynchronous, it can return a promise to signal success or failure; a rejected promise - will error the stream. Any thrown exceptions will be re-thrown by the {{ReadableStream()}} constructor.

- +

Constructor, methods, and properties

-
pull(controller)
-
-

A function that is called whenever the stream's internal queue of chunks becomes not full, i.e. whenever - the queue's desired size becomes positive. Generally, it - will be called repeatedly until the queue reaches its high water mark (i.e. until the desired size becomes non-positive).

- -

For push sources, this can be used to resume a paused flow, as in [[#example-rs-push-backpressure]]. For - pull sources, it is used to acquire new chunks to enqueue into the stream, as in - [[#example-rs-pull]].

- -

This function will not be called until {{underlying source/start()}} successfully completes. Additionally, it - will only be called repeatedly if it enqueues at least one chunk or fulfills a BYOB request; a no-op - {{underlying source/pull()}} implementation will not be continually called.

- -

If the function returns a promise, then it will not be called again until that promise fulfills. (If the - promise rejects, the stream will become errored.) This is mainly used in the case of pull sources, where the - promise returned represents the process of acquiring a new chunk. Throwing an exception is treated the same as - returning a rejected promise.

-
- -
cancel(reason)
+
+
stream = new {{ReadableStream/constructor(underlyingSource, strategy)|ReadableStream}}(underlyingSource[, strategy])
-

A function that is called whenever the consumer cancels the stream, - via {{ReadableStream/cancel()|stream.cancel()}}, {{ReadableStreamDefaultReader/cancel()|defaultReader.cancel()}}, or - {{ReadableStreamBYOBReader/cancel()|byobReader.cancel()}}. It takes as its argument the same value as was passed to - those methods by the consumer.

- -

Readable streams can additionally be canceled under certain conditions during piping; see the definition - of the {{ReadableStream/pipeTo()}} method for more details.

- -

For all streams, this is generally used to release access to the underlying resource; see for example - [[#example-rs-push-no-backpressure]].

- -

If the shutdown process is asynchronous, it can return a promise to signal success or failure; the result will be - communicated via the return value of the cancel() method that was called. Additionally, a rejected - promise will error the stream, instead of letting it close. Throwing an exception is treated the same as returning - a rejected promise.

-
- -
type (byte streams only)
+

Creates a new {{ReadableStream}} wrapping the provided [=underlying source=]. See + [[#underlying-source-api]] for more details on the underlyingSource argument. + +

The |strategy| argument represents the stream's [=queuing strategy=], as described in + [[#qs-api]]. If it is not provided, the default behavior will be the same as a + {{CountQueuingStrategy}} with a [=high water mark=] of 1. + +

isLocked = stream.{{ReadableStream/locked}} +
+

Returns whether or not the readable stream is [=locked to a reader=]. + +

await stream.{{ReadableStream/cancel(reason)|cancel}}([ reason ]) +
+

[=cancel a readable stream|Cancels=] the stream, signaling a loss of interest in the stream by + a consumer. The supplied reason argument will be given to the underlying source's + {{UnderlyingSource/cancel|cancel()}} method, which might or might not use it. + +

The returned promise will fulfill if the stream shuts down successfully, or reject if the + underlying source signaled that there was an error doing so. Additionally, it will reject with a + {{TypeError}} (without attempting to cancel the stream) if the stream is currently [=locked to a + reader|locked=]. + +

reader = stream.{{ReadableStream/getReader(options)|getReader}}() +
+

Creates a {{ReadableStreamDefaultReader}} and [=locked to a reader|locks=] the stream to the + new reader. While the stream is locked, no other reader can be acquired until this one is + [=release a read lock|released=]. + +

This functionality is especially useful for creating abstractions that desire the ability to + consume a stream in its entirety. By getting a reader for the stream, you can ensure nobody else + can interleave reads with yours or cancel the stream, which would interfere with your + abstraction. + +

reader = stream.{{ReadableStream/getReader(options)|getReader}}({ {{ReadableStreamGetReaderOptions/mode}}: "{{ReadableStreamReaderMode/byob}}" })
-

Can be set to "bytes" to signal that the constructed {{ReadableStream}} is a readable byte - stream. This ensures that the resulting {{ReadableStream}} will successfully be able to vend BYOB readers - via its {{ReadableStream/getReader()}} method. It also affects the controller argument passed to the - {{underlying source/start()}} and {{underlying source/pull()}} methods; see below.

+

Creates a {{ReadableStreamBYOBReader}} and [=locked to a reader|locks=] the stream to the new + reader. + +

This call behaves the same way as the no-argument variant, except that it only works on + [=readable byte streams=], i.e. streams which were constructed specifically with the ability to + handle "bring your own buffer" reading. The returned [=BYOB reader=] provides the ability to + directly read individual [=chunks=] from the stream via its {{ReadableStreamBYOBReader/read()}} + method, into developer-supplied buffers, allowing more precise control over allocation. + +

readable = stream.{{ReadableStream/pipeThrough(transform, options)|pipeThrough}}({ {{ReadableWritablePair/writable}}, {{ReadableWritablePair/readable}} }[, { {{StreamPipeOptions/preventClose}}, {{StreamPipeOptions/preventCancel}}, {{StreamPipeOptions/preventCancel}}, {{StreamPipeOptions/signal}} }])
+
+

Provides a convenient, chainable way of [=piping=] this [=readable stream=] through a + [=transform stream=] (or any other { writable, readable } pair). It simply pipes the + stream into the writable side of the supplied pair, and returns the readable side for further use. + +

Piping a stream will [=locked to a reader|lock=] it for the duration of the pipe, preventing + any other consumer from acquiring a reader. + +

await stream.{{ReadableStream/pipeTo(destination, options)|pipeTo}}(destination[, { {{StreamPipeOptions/preventClose}}, {{StreamPipeOptions/preventCancel}}, {{StreamPipeOptions/preventCancel}}, {{StreamPipeOptions/signal}} }])
+
+

[=piping|Pipes=] this [=readable stream=] to a given [=writable stream=] |destination|. The + way in which the piping process behaves under various error conditions can be customized with a + number of passed options. It returns a promise that fulfills when the piping process completes + successfully, or rejects if any errors were encountered. + + Piping a stream will [=locked to a reader|lock=] it for the duration of the pipe, preventing any + other consumer from acquiring a reader. -

For an example of how to set up a readable byte stream, including using the different controller interface, see - [[#example-rbs-push]].

- -

Setting any value other than "bytes" or undefined will cause the - {{ReadableStream()}} constructor to throw an exception.

-
+ Errors and closures of the source and destination streams propagate as follows: -
autoAllocateChunkSize (byte streams only)
-
-

Can be set to a positive integer to cause the implementation to automatically allocate buffers for the - underlying source code to write into. In this case, when a consumer is using a default reader, the - stream implementation will automatically allocate an {{ArrayBuffer}} of the given size, so that - {{ReadableByteStreamController/byobRequest|controller.byobRequest}} is always present, as if the consumer was using - a BYOB reader.

- -

This is generally used to cut down on the amount of code needed to handle consumers that use default readers, as - can be seen by comparing [[#example-rbs-push]] without auto-allocation to [[#example-rbs-pull]] with - auto-allocation.

-
+ * An error in this source [=readable stream=] will [=abort a writable stream|abort=] + |destination|, unless {{StreamPipeOptions/preventAbort}} is truthy. The returned promise will be + rejected with the source's error, or with any error that occurs during aborting the destination. + + * An error in |destination| will [=cancel a readable stream|cancel=] this source [=readable + stream=], unless {{StreamPipeOptions/preventCancel}} is truthy. The returned promise will be + rejected with the destination's error, or with any error that occurs during canceling the source. + + * When this source [=readable stream=] closes, |destination| will be closed, unless + {{StreamPipeOptions/preventCancel}} is truthy. The returned promise will be fulfilled once this + process completes, unless an error is encountered while closing the destination, in which case + it will be rejected with that error. + + * If |destination| starts out closed or closing, this source [=readable stream=] will be [=cancel + a readable stream|canceled=], unless {{StreamPipeOptions/preventCancel}} is true. The returned + promise will be rejected with an error indicating piping to a closed stream failed, or with any + error that occurs during canceling the source. + +

The {{StreamPipeOptions/signal}} option can be set to an {{AbortSignal}} to allow aborting an + ongoing pipe operation via the corresponding {{AbortController}}. In this case, this source + [=readable stream=] will be [=cancel a readable stream|canceled=], and |destination| [=abort a + writable stream|aborted=], unless the respective options {{StreamPipeOptions/preventCancel}} or + {{StreamPipeOptions/preventAbort}} are set. + +

[branch1, branch2] = stream.{{ReadableStream/tee()|tee}}() +
+

[=tee a readable stream|Tees=] this readable stream, returning a two-element array containing + the two resulting branches as new {{ReadableStream}} instances. + +

Teeing a stream will [=locked to a reader|lock=] it, preventing any other consumer from + acquiring a reader. To [=cancel a readable stream|cancel=] the stream, cancel both of the + resulting branches; a composite cancellation reason will then be propagated to the stream's + [=underlying source=]. + +

Note that the [=chunks=] seen in each branch will be the same object. If the chunks are not + immutable, this could allow interference between the two branches.

-The type of the controller argument passed to the {{underlying source/start()}} and -{{underlying source/pull()}} methods depends on the value of the type -option. If type is set to undefined (including via omission), -controller will be a {{ReadableStreamDefaultController}}. If it's set to "bytes", -controller will be a {{ReadableByteStreamController}}. - +
+ The ReadableStream(|underlyingSource|, + |strategy|) constructor steps are: + + 1. If |underlyingSource| is missing, set it to null. + 1. Let |underlyingSourceDict| be |underlyingSource|, [=converted to an IDL value=] of type + {{UnderlyingSource}}. +

We cannot declare the |underlyingSource| argument as having the + {{UnderlyingSource}} type directly, because doing so would lose the reference to the original + object. We need to retain the object so we can [=invoke=] the various methods on it. + 1. Perform ! [$InitializeReadableStream$]([=this=]). + 1. If |underlyingSourceDict|["{{UnderlyingSource/type}}"] is "{{ReadableStreamType/bytes}}": + 1. If |strategy|["{{QueuingStrategy/size}}"] [=map/exists=], throw a {{RangeError}} exception. + 1. Let |highWaterMark| be ? [$ExtractHighWaterMark$](|strategy|, 0). + 1. Perform ? [$SetUpReadableByteStreamControllerFromUnderlyingSource$]([=this=], + |underlyingSource|, |underlyingSourceDict|, |highWaterMark|). + 1. Otherwise, + 1. Assert: |underlyingSourceDict|["{{UnderlyingSource/type}}"] does not [=map/exist=]. + 1. Let |sizeAlgorithm| be ! [$ExtractSizeAlgorithm$](|strategy|). + 1. Let |highWaterMark| be ? [$ExtractHighWaterMark$](|strategy|, 1). + 1. Perform ? [$SetUpReadableStreamDefaultControllerFromUnderlyingSource$]([=this=], + |underlyingSource|, |underlyingSourceDict|, |highWaterMark|, |sizeAlgorithm|).

-

Properties of the {{ReadableStream}} prototype

+
+ The locked attribute's getter steps are: -
get locked
- -
- The locked getter returns whether or not the readable stream is locked to a reader. + 1. Return ! [$IsReadableStreamLocked$]([=this=]).
- - 1. If ! IsReadableStream(*this*) is *false*, throw a *TypeError* exception. - 1. Return ! IsReadableStreamLocked(*this*). - +
+ The cancel(|reason|) method steps are: -
cancel(reason)
- -
- The cancel method cancels the stream, signaling a loss of interest - in the stream by a consumer. The supplied reason argument will be given to the underlying source's - {{underlying source/cancel()}} method, which might or might not use it. + 1. If ! [$IsReadableStreamLocked$]([=this=]) is true, return [=a promise rejected with=] a + {{TypeError}} exception. + 1. Return ! [$ReadableStreamCancel$]([=this=], |reason|).
- - 1. If ! IsReadableStream(*this*) is *false*, return a promise rejected with a *TypeError* exception. - 1. If ! IsReadableStreamLocked(*this*) is *true*, return a promise rejected with a *TypeError* exception. - 1. Return ! ReadableStreamCancel(*this*, _reason_). - +
+ The getReader(|options|) method steps + are: -
getIterator({ preventCancel } = {})
+ 1. If |options|["{{ReadableStreamGetReaderOptions/mode}}"] does not [=map/exist=], return ? + [$AcquireReadableStreamDefaultReader$]([=this=], true). + 1. Assert: |options|["{{ReadableStreamGetReaderOptions/mode}}"] is + "{{ReadableStreamReaderMode/byob}}". + 1. Return ? [$AcquireReadableStreamBYOBReader$]([=this=], true). -
- 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. -
+
+ An example of an abstraction that might benefit from using a reader is a function like the + following, which is designed to read an entire readable stream into memory as an array of + [=chunks=]. - - 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_. - + + function readAllChunks(readableStream) { + const reader = readableStream.getReader(); + const chunks = []; -<h5 id="rs-get-reader" method for="ReadableStream">getReader({ <var ignore>mode</var> } = {})</h5> + return pump(); -<div class="note"> - The <code>getReader</code> method creates a reader of the type specified by the <code>mode</code> option and <a - lt="locked to a reader">locks</a> the stream to the new reader. While the stream is locked, no other reader can be - acquired until this one is <a lt="release a read lock">released</a>. - - This functionality is especially useful for creating abstractions that desire the ability to consume a stream in its - entirety. By getting a reader for the stream, you can ensure nobody else can interleave reads with yours or cancel - the stream, which would interfere with your abstraction. - - When <code>mode</code> is <emu-val>undefined</emu-val>, the method creates a <a>default reader</a> (an instance of - {{ReadableStreamDefaultReader}}). The reader provides the ability to directly read individual <a>chunks</a> from the - stream via the reader's {{ReadableStreamDefaultReader/read()}} method. - - When <code>mode</code> is <code>"byob"</code>, the <code>getReader</code> method creates a <a>BYOB reader</a> (an - instance of {{ReadableStreamBYOBReader}}). This feature only works on <a>readable byte streams</a>, i.e. streams which - were constructed specifically with the ability to handle "bring your own buffer" reading. The reader provides the - ability to directly read individual <a>chunks</a> from the stream via the reader's {{ReadableStreamBYOBReader/read()}} - method, into developer-supplied buffers, allowing more precise control over allocation. -</div> - -<emu-alg> - 1. If ! IsReadableStream(*this*) is *false*, throw a *TypeError* exception. - 1. If _mode_ is *undefined*, return ? AcquireReadableStreamDefaultReader(*this*, *true*). - 1. Set _mode_ to ? ToString(_mode_). - 1. If _mode_ is `"byob"`, return ? AcquireReadableStreamBYOBReader(*this*, *true*). - 1. Throw a *RangeError* exception. -</emu-alg> - -<div class="example" id="example-read-all-chunks"> - An example of an abstraction that might benefit from using a reader is a function like the following, which is - designed to read an entire readable stream into memory as an array of <a>chunks</a>. - - <pre><code class="lang-javascript"> - function readAllChunks(readableStream) { - const reader = readableStream.getReader(); - const chunks = []; - - return pump(); - - function pump() { - return reader.read().then(({ value, done }) => { - if (done) { - return chunks; - } + function pump() { + return reader.read().then(({ value, done }) => { + if (done) { + return chunks; + } - chunks.push(value); - return pump(); - }); - } + chunks.push(value); + return pump(); + }); } - </code></pre> + } + - Note how the first thing it does is obtain a reader, and from then on it uses the reader exclusively. This ensures - that no other consumer can interfere with the stream, either by reading chunks or by - canceling the stream. + Note how the first thing it does is obtain a reader, and from then on it uses the reader + exclusively. This ensures that no other consumer can interfere with the stream, either by reading + chunks or by [=cancel a readable stream|canceling=] the stream. +
-
pipeThrough({ -writable, readable }, { preventClose, preventAbort, -preventCancel, signal } = {})
- -
- The pipeThrough method provides a convenient, chainable way of piping this readable stream - through a transform stream (or any other { writable, readable } pair). It simply pipes the stream - into the writable side of the supplied pair, and returns the readable side for further use. - - Piping a stream will lock it for the duration of the pipe, preventing any other - consumer from acquiring a reader. -
- - - 1. If ! IsReadableStream(*this*) is *false*, throw a *TypeError* exception. - 1. If ! IsWritableStream(_writable_) is *false*, throw a *TypeError* exception. - 1. If ! IsReadableStream(_readable_) is *false*, throw a *TypeError* exception. - 1. Set _preventClose_ to ! ToBoolean(_preventClose_), set _preventAbort_ to ! ToBoolean(_preventAbort_), and set - _preventCancel_ to ! ToBoolean(_preventCancel_). - 1. If _signal_ is not *undefined*, and _signal_ is not an instance of the `AbortSignal` interface, throw a - *TypeError* exception. - 1. If ! IsReadableStreamLocked(*this*) is *true*, throw a *TypeError* exception. - 1. If ! IsWritableStreamLocked(_writable_) is *true*, throw a *TypeError* exception. - 1. Let _promise_ be ! ReadableStreamPipeTo(*this*, _writable_, _preventClose_, _preventAbort_, _preventCancel_, - _signal_). - 1. Set _promise_.[[PromiseIsHandled]] to *true*. - 1. Return _readable_. - - -
- A typical example of constructing pipe chain using {{ReadableStream/pipeThrough(transform, options)}} would - look like - -

-    httpResponseBody
-      .pipeThrough(decompressorTransform)
-      .pipeThrough(ignoreNonImageFilesTransform)
-      .pipeTo(mediaGallery);
-  
-
- -
pipeTo(dest, -{ preventClose, preventAbort, preventCancel, signal } = {})
- -
- The pipeTo method pipes this readable stream to a given writable - stream. The way in which the piping process behaves under various error conditions can be customized with a - number of passed options. It returns a promise that fulfills when the piping process completes successfully, or - rejects if any errors were encountered. - - Piping a stream will lock it for the duration of the pipe, preventing any other - consumer from acquiring a reader. - - Errors and closures of the source and destination streams propagate as follows: - -
    -
  • An error in the source readable stream will abort the destination - writable stream, unless preventAbort is truthy. The returned promise will be rejected with the - source's error, or with any error that occurs during aborting the destination.

  • - -
  • An error in the destination writable stream will cancel the - source readable stream, unless preventCancel is truthy. The returned promise will be rejected - with the destination's error, or with any error that occurs during canceling the source.

  • - -
  • When the source readable stream closes, the destination writable stream will be closed, unless - preventClose is true. The returned promise will be fulfilled once this process completes, unless an - error is encountered while closing the destination, in which case it will be rejected with that error.

  • - -
  • If the destination writable stream starts out closed or closing, the source readable stream - will be canceled, unless preventCancel is true. The returned - promise will be rejected with an error indicating piping to a closed stream failed, or with any error that occurs - during canceling the source.

  • -
- - The signal option can be set to an {{AbortSignal}} to allow aborting an ongoing pipe operation via the - corresponding {{AbortController}}. In this case, the source readable stream will be canceled, and the destination writable stream aborted, unless - the respective options preventCancel or preventAbort are set. - +
+ The pipeThrough(|transform|, |options|) + method steps are: + + 1. If ! [$IsReadableStreamLocked$]([=this=]) is true, throw a {{TypeError}} exception. + 1. If ! [$IsWritableStreamLocked$](|transform|["{{ReadableWritablePair/writable}}"]) is true, throw + a {{TypeError}} exception. + 1. Let |signal| be |options|["{{StreamPipeOptions/signal}}"] if it [=map/exists=], or undefined + otherwise. + 1. Let |promise| be ! [$ReadableStreamPipeTo$]([=this=], + |transform|["{{ReadableWritablePair/writable}}"], + |options|["{{StreamPipeOptions/preventClose}}"], + |options|["{{StreamPipeOptions/preventAbort}}"], + |options|["{{StreamPipeOptions/preventCancel}}"], |signal|). + 1. Set |promise|.\[[PromiseIsHandled]] to true. + 1. Return |transform|["{{ReadableWritablePair/readable}}"]. + +
+ A typical example of constructing [=pipe chain=] using {{ReadableStream/pipeThrough(transform, + options)}} would look like + + + httpResponseBody + .pipeThrough(decompressorTransform) + .pipeThrough(ignoreNonImageFilesTransform) + .pipeTo(mediaGallery); + +
- - 1. If ! IsReadableStream(*this*) is *false*, return a promise rejected with a *TypeError* exception. - 1. If ! IsWritableStream(_dest_) is *false*, return a promise rejected with a *TypeError* exception. - 1. Set _preventClose_ to ! ToBoolean(_preventClose_), set _preventAbort_ to ! ToBoolean(_preventAbort_), and set - _preventCancel_ to ! ToBoolean(_preventCancel_). - 1. If _signal_ is not *undefined*, and _signal_ is not an instance of the `AbortSignal` interface, return - a promise rejected with a *TypeError* exception. - 1. If ! IsReadableStreamLocked(*this*) is *true*, return a promise rejected with a *TypeError* exception. - 1. If ! IsWritableStreamLocked(_dest_) is *true*, return a promise rejected with a *TypeError* exception. - 1. Return ! ReadableStreamPipeTo(*this*, _dest_, _preventClose_, _preventAbort_, _preventCancel_, _signal_). - - -
tee()
- -
- The tee method tees this readable stream, returning a two-element - array containing the two resulting branches as new {{ReadableStream}} instances. - - Teeing a stream will lock it, preventing any other consumer from acquiring a reader. - To cancel the stream, cancel both of the resulting branches; a composite - cancellation reason will then be propagated to the stream's underlying source. - - Note that the chunks seen in each branch will be the same object. If the chunks are not immutable, this could - allow interference between the two branches. +
+ The pipeTo(|destination|, |options|) + method steps are: + + 1. If ! [$IsReadableStreamLocked$]([=this=]) is true, return [=a promise rejected with=] a + {{TypeError}} exception. + 1. If ! [$IsWritableStreamLocked$](|destination|) is true, return [=a promise rejected with=] a + {{TypeError}} exception. + 1. Let |signal| be |options|["{{StreamPipeOptions/signal}}"] if it [=map/exists=], or undefined + otherwise. + 1. Return ! [$ReadableStreamPipeTo$]([=this=], |destination|, + |options|["{{StreamPipeOptions/preventClose}}"], + |options|["{{StreamPipeOptions/preventAbort}}"], + |options|["{{StreamPipeOptions/preventCancel}}"], |signal|).
- - 1. If ! IsReadableStream(*this*) is *false*, throw a *TypeError* exception. - 1. Let _branches_ be ? ReadableStreamTee(*this*, *false*). - 1. Return ! CreateArrayFromList(_branches_). - - -
- Teeing a stream is most useful when you wish to let two independent consumers read from the stream in parallel, - perhaps even at different speeds. For example, given a writable stream cacheEntry representing an - on-disk file, and another writable stream httpRequestBody representing an upload to a remote server, - you could pipe the same readable stream to both destinations at once: - -

-    const [forLocal, forRemote] = readableStream.tee();
-
-    Promise.all([
-      forLocal.pipeTo(cacheEntry),
-      forRemote.pipeTo(httpRequestBody)
-    ])
-    .then(() => console.log("Saved the stream to the cache and also uploaded it!"))
-    .catch(e => console.error("Either caching or uploading failed: ", e));
-  
+
+ The tee() method steps are: + + 1. Return ? [$ReadableStreamTee$]([=this=], false). + +
+ Teeing a stream is most useful when you wish to let two independent consumers read from the stream + in parallel, perhaps even at different speeds. For example, given a writable stream + cacheEntry representing an on-disk file, and another writable stream + httpRequestBody representing an upload to a remote server, you could pipe the same + readable stream to both destinations at once: + + + const [forLocal, forRemote] = readableStream.tee(); + + Promise.all([ + forLocal.pipeTo(cacheEntry), + forRemote.pipeTo(httpRequestBody) + ]) + .then(() => console.log("Saved the stream to the cache and also uploaded it!")) + .catch(e => console.error("Either caching or uploading failed: ", e)); + +
- -
[@@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 -
+

Asynchronous iteration

-

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 reacting to ! ReadableStreamDefaultReaderRead(_reader_) with the following fulfillment - steps given the argument _result_: - 1. Assert: Type(_result_) is Object. - 1. Let _done_ be ! Get(_result_, `"done"`). - 1. Assert: Type(_done_) is Boolean. - 1. If _done_ is *true*, perform ! ReadableStreamReaderGenericRelease(_reader_). - 1. Let _value_ be ! Get(_result_, `"value"`). - 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 reacting to _result_ with a fulfillment step 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 -specifications, instead of just being part of the implementation of this spec's classes. - -

AcquireReadableStreamBYOBReader ( stream[, forAuthorCode ] )

- -This abstract operation is meant to be called from other specifications that may wish to acquire a BYOB reader -for a given stream. - - - 1. If _forAuthorCode_ was not passed, set it to *false*. - 1. Let _reader_ be ? Construct(`ReadableStreamBYOBReader`, « _stream_ »). - 1. Set _reader_.[[forAuthorCode]] to _forAuthorCode_. - 1. Return _reader_. - - -

AcquireReadableStreamDefaultReader ( stream[, forAuthorCode ] )

- -This abstract operation is meant to be called from other specifications that may wish to acquire a default -reader for a given stream. - -

Other specifications ought to leave forAuthorCode as its default value of -false, unless they are planning to directly expose the resulting { value, done } object -to authors. See the note regarding ReadableStreamCreateReadResult for more -information.

- - - 1. If _forAuthorCode_ was not passed, set it to *false*. - 1. Let _reader_ be ? Construct(`ReadableStreamDefaultReader`, « _stream_ »). - 1. Set _reader_.[[forAuthorCode]] to _forAuthorCode_. - 1. Return _reader_. - - -

CreateReadableStream ( -startAlgorithm, pullAlgorithm, cancelAlgorithm [, highWaterMark [, -sizeAlgorithm ] ] )

- -This abstract operation is meant to be called from other specifications that wish to create {{ReadableStream}} -instances. The pullAlgorithm and cancelAlgorithm algorithms must return -promises; if supplied, sizeAlgorithm must be an algorithm accepting chunk objects and returning a -number; and if supplied, highWaterMark must be a non-negative, non-NaN number. - -

CreateReadableStream throws an exception if and only if the supplied startAlgorithm -throws.

- - - 1. If _highWaterMark_ was not passed, set it to *1*. - 1. If _sizeAlgorithm_ was not passed, set it to an algorithm that returns *1*. - 1. Assert: ! IsNonNegativeNumber(_highWaterMark_) is *true*. - 1. Let _stream_ be ObjectCreate(the original value of `ReadableStream`'s `prototype` property). - 1. Perform ! InitializeReadableStream(_stream_). - 1. Let _controller_ be ObjectCreate(the original value of `ReadableStreamDefaultController`'s `prototype` - property). - 1. Perform ? SetUpReadableStreamDefaultController(_stream_, _controller_, _startAlgorithm_, _pullAlgorithm_, - _cancelAlgorithm_, _highWaterMark_, _sizeAlgorithm_). - 1. Return _stream_. - - -

CreateReadableByteStream ( -startAlgorithm, pullAlgorithm, cancelAlgorithm [, highWaterMark [, -autoAllocateChunkSize ] ] )

- -This abstract operation is meant to be called from other specifications that wish to create {{ReadableStream}} -instances of type "bytes". The pullAlgorithm and cancelAlgorithm algorithms must return -promises; if supplied, highWaterMark must be a non-negative, non-NaN number, and if supplied, -autoAllocateChunkSize must be a positive integer. - -

CreateReadableByteStream throws an exception if and only if the supplied startAlgorithm -throws.

- - - 1. If _highWaterMark_ was not passed, set it to *0*. - 1. If _autoAllocateChunkSize_ was not passed, set it to *undefined*. - 1. Assert: ! IsNonNegativeNumber(_highWaterMark_) is *true*. - 1. If _autoAllocateChunkSize_ is not *undefined*, - 1. Assert: ! IsInteger(_autoAllocateChunkSize_) is *true*. - 1. Assert: _autoAllocateChunkSize_ is positive. - 1. Let _stream_ be ObjectCreate(the original value of `ReadableStream`'s `prototype` property). - 1. Perform ! InitializeReadableStream(_stream_). - 1. Let _controller_ be ObjectCreate(the original value of `ReadableByteStreamController`'s `prototype` - property). - 1. Perform ? SetUpReadableByteStreamController(_stream_, _controller_, _startAlgorithm_, _pullAlgorithm_, - _cancelAlgorithm_, _highWaterMark_, _autoAllocateChunkSize_). - 1. Return _stream_. - - -

InitializeReadableStream ( -stream )

- - - 1. Set _stream_.[[state]] to `"readable"`. - 1. Set _stream_.[[reader]] and _stream_.[[storedError]] to *undefined*. - 1. Set _stream_.[[disturbed]] to *false*. - - -

IsReadableStream ( x )

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

IsReadableStreamDisturbed ( -stream )

- -This abstract operation is meant to be called from other specifications that may wish to query whether or not a -readable stream has ever been read from or canceled. - - - 1. Assert: ! IsReadableStream(_stream_) is *true*. - 1. Return _stream_.[[disturbed]]. - - -

IsReadableStreamLocked ( -stream )

- -This abstract operation is meant to be called from other specifications that may wish to query whether or not a -readable stream is locked to a reader. - - - 1. Assert: ! IsReadableStream(_stream_) is *true*. - 1. If _stream_.[[reader]] is *undefined*, return *false*. - 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 )

- -This abstract operation is meant to be called from other specifications that may wish to tee a given readable stream. - -The second argument, cloneForBranch2, governs whether or not the data from the original stream will be cloned -(using HTML's serializable objects framework) before appearing in the second of the returned branches. This is -useful for scenarios where both branches are to be consumed in such a way that they might otherwise interfere with each -other, such as by transferring their chunks. However, it does introduce a -noticeable asymmetry between the two branches, and limits the possible chunks to serializable ones. [[!HTML]] - -

In this standard ReadableStreamTee is always called with cloneForBranch2 set to -false; other specifications pass true.

- - - 1. Assert: ! IsReadableStream(_stream_) is *true*. - 1. Assert: Type(_cloneForBranch2_) is Boolean. - 1. Let _reader_ be ? AcquireReadableStreamDefaultReader(_stream_). - 1. Let _reading_ be *false*. - 1. Let _canceled1_ be *false*. - 1. Let _canceled2_ be *false*. - 1. Let _reason1_ be *undefined*. - 1. Let _reason2_ be *undefined*. - 1. Let _branch1_ be *undefined*. - 1. Let _branch2_ be *undefined*. - 1. Let _cancelPromise_ be a new promise. - 1. Let _pullAlgorithm_ be the following steps: - 1. If _reading_ is *true*, return a promise resolved with *undefined*. - 1. Set _reading_ to *true*. - 1. Let _readPromise_ be the result of reacting to ! ReadableStreamDefaultReaderRead(_reader_) with the - following fulfillment steps given the argument _result_: - 1. Set _reading_ to *false*. - 1. Assert: Type(_result_) is Object. - 1. Let _done_ be ! Get(_result_, `"done"`). - 1. Assert: Type(_done_) is Boolean. - 1. If _done_ is *true*, - 1. If _canceled1_ is *false*, - 1. Perform ! ReadableStreamDefaultControllerClose(_branch1_.[[readableStreamController]]). - 1. If _canceled2_ is *false*, - 1. Perform ! ReadableStreamDefaultControllerClose(_branch2_.[[readableStreamController]]). - 1. Return. - 1. Let _value_ be ! Get(_result_, `"value"`). - 1. Let _value1_ and _value2_ be _value_. - 1. If _canceled2_ is *false* and _cloneForBranch2_ is *true*, set _value2_ to ? StructuredDeserialize(? StructuredSerialize(_value2_), the current Realm - Record). - 1. If _canceled1_ is *false*, perform ? - ReadableStreamDefaultControllerEnqueue(_branch1_.[[readableStreamController]], _value1_). - 1. If _canceled2_ is *false*, perform ? - ReadableStreamDefaultControllerEnqueue(_branch2_.[[readableStreamController]], _value2_). - 1. Set _readPromise_.[[PromiseIsHandled]] to *true*. - 1. Return a promise resolved with *undefined*. - 1. Let _cancel1Algorithm_ be the following steps, taking a _reason_ argument: - 1. Set _canceled1_ to *true*. - 1. Set _reason1_ to _reason_. - 1. If _canceled2_ is *true*, - 1. Let _compositeReason_ be ! CreateArrayFromList(« _reason1_, _reason2_ »). - 1. Let _cancelResult_ be ! ReadableStreamCancel(_stream_, _compositeReason_). - 1. Resolve _cancelPromise_ with _cancelResult_. - 1. Return _cancelPromise_. - 1. Let _cancel2Algorithm_ be the following steps, taking a _reason_ argument: - 1. Set _canceled2_ to *true*. - 1. Set _reason2_ to _reason_. - 1. If _canceled1_ is *true*, - 1. Let _compositeReason_ be ! CreateArrayFromList(« _reason1_, _reason2_ »). - 1. Let _cancelResult_ be ! ReadableStreamCancel(_stream_, _compositeReason_). - 1. Resolve _cancelPromise_ with _cancelResult_. - 1. Return _cancelPromise_. - 1. Let _startAlgorithm_ be an algorithm that returns *undefined*. - 1. Set _branch1_ to ! CreateReadableStream(_startAlgorithm_, _pullAlgorithm_, _cancel1Algorithm_). - 1. Set _branch2_ to ! CreateReadableStream(_startAlgorithm_, _pullAlgorithm_, _cancel2Algorithm_). - 1. Upon rejection of _reader_.[[closedPromise]] with reason _r_, - 1. Perform ! ReadableStreamDefaultControllerError(_branch1_.[[readableStreamController]], _r_). - 1. Perform ! ReadableStreamDefaultControllerError(_branch2_.[[readableStreamController]], _r_). - 1. Return « _branch1_, _branch2_ ». - - -

ReadableStreamPipeTo ( -source, dest, preventClose, preventAbort, preventCancel, -signal )

- - - 1. Assert: ! IsReadableStream(_source_) is *true*. - 1. Assert: ! IsWritableStream(_dest_) is *true*. - 1. Assert: Type(_preventClose_) is Boolean, Type(_preventAbort_) is Boolean, and Type(_preventCancel_) is Boolean. - 1. Assert: _signal_ is *undefined* or _signal_ is an instance of the `AbortSignal` interface. - 1. Assert: ! IsReadableStreamLocked(_source_) is *false*. - 1. Assert: ! IsWritableStreamLocked(_dest_) is *false*. - 1. If ! IsReadableByteStreamController(_source_.[[readableStreamController]]) is *true*, let _reader_ be either ! - AcquireReadableStreamBYOBReader(_source_) or ! AcquireReadableStreamDefaultReader(_source_), at the user agent's - discretion. - 1. Otherwise, let _reader_ be ! AcquireReadableStreamDefaultReader(_source_). - 1. Let _writer_ be ! AcquireWritableStreamDefaultWriter(_dest_). - 1. Set _source_.[[disturbed]] to *true*. - 1. Let _shuttingDown_ be *false*. - 1. Let _promise_ be a new promise. - 1. If _signal_ is not *undefined*, - 1. Let _abortAlgorithm_ be the following steps: - 1. Let _error_ be a new "`AbortError`" `DOMException`. - 1. Let _actions_ be an empty ordered set. - 1. If _preventAbort_ is *false*, append the following action to _actions_: - 1. If _dest_.[[state]] is `"writable"`, return ! WritableStreamAbort(_dest_, _error_). - 1. Otherwise, return a promise resolved with *undefined*. - 1. If _preventCancel_ is *false*, append the following action action to _actions_: - 1. If _source_.[[state]] is `"readable"`, return ! ReadableStreamCancel(_source_, _error_). - 1. Otherwise, return a promise resolved with *undefined*. - 1. Shutdown with an action consisting of - getting a promise to wait for all of the actions in _actions_, and with _error_. - 1. If _signal_'s aborted flag is set, perform _abortAlgorithm_ and return _promise_. - 1. Add _abortAlgorithm_ to _signal_. - 1. In parallel but not really; see #905, using _reader_ and _writer_, read all - chunks from _source_ and write them to _dest_. Due to the locking provided by the reader and writer, the exact - manner in which this happens is not observable to author code, and so there is flexibility in how this is done. The - following constraints apply regardless of the exact algorithm used: - * Public API must not be used: while reading or writing, or performing any of the operations - below, the JavaScript-modifiable reader, writer, and stream APIs (i.e. methods on the appropriate prototypes) - must not be used. Instead, the streams must be manipulated directly. - * Backpressure must be enforced: - * While WritableStreamDefaultWriterGetDesiredSize(_writer_) is ≤ *0* or is *null*, the user agent must not read - from _reader_. - * If _reader_ is a BYOB reader, WritableStreamDefaultWriterGetDesiredSize(_writer_) should be used as a - basis to determine the size of the chunks read from _reader_. -

It's frequently inefficient to read chunks that are too small or too large. Other information - might be factored in to determine the optimal chunk size.

- * Reads or writes should not be delayed for reasons other than these backpressure signals. -

An implementation that waits for each write to successfully - complete before proceeding to the next read/write operation violates this recommendation. In doing so, such an - implementation makes the internal queue of _dest_ useless, as it ensures _dest_ always contains at most - one queued chunk.

- * Shutdown must stop activity: if _shuttingDown_ becomes *true*, the user agent must not - initiate further reads from _reader_, and must only perform writes of already-read chunks, as described - below. In particular, the user agent must check the below conditions before performing any reads or writes, - since they might lead to immediate shutdown. - * Error and close states must be propagated: the following conditions must be applied in order. - 1. Errors must be propagated forward: if _source_.[[state]] is or becomes `"errored"`, then - 1. If _preventAbort_ is *false*, shutdown with an action of ! - WritableStreamAbort(_dest_, _source_.[[storedError]]) and with _source_.[[storedError]]. - 1. Otherwise, shutdown with _source_.[[storedError]]. - 1. Errors must be propagated backward: if _dest_.[[state]] is or becomes `"errored"`, then - 1. If _preventCancel_ is *false*, shutdown with an action of ! - ReadableStreamCancel(_source_, _dest_.[[storedError]]) and with _dest_.[[storedError]]. - 1. Otherwise, shutdown with _dest_.[[storedError]]. - 1. Closing must be propagated forward: if _source_.[[state]] is or becomes `"closed"`, then - 1. If _preventClose_ is *false*, shutdown with an action of ! - WritableStreamDefaultWriterCloseWithErrorPropagation(_writer_). - 1. Otherwise, shutdown. - 1. Closing must be propagated backward: if ! WritableStreamCloseQueuedOrInFlight(_dest_) is *true* - or _dest_.[[state]] is `"closed"`, then - 1. Assert: no chunks have been read or written. - 1. Let _destClosed_ be a new *TypeError*. - 1. If _preventCancel_ is *false*, shutdown with an action of ! - ReadableStreamCancel(_source_, _destClosed_) and with _destClosed_. - 1. Otherwise, shutdown with _destClosed_. - * Shutdown with an action: if any of the above requirements ask to - shutdown with an action _action_, optionally with an error _originalError_, then: - 1. If _shuttingDown_ is *true*, abort these substeps. - 1. Set _shuttingDown_ to *true*. - 1. If _dest_.[[state]] is `"writable"` and ! WritableStreamCloseQueuedOrInFlight(_dest_) is *false*, - 1. If any chunks have been read but not yet written, write them to _dest_. - 1. Wait until every chunk that has been read has been written (i.e. the corresponding promises have - settled). - 1. Let _p_ be the result of performing _action_. - 1. Upon fulfillment of _p_, finalize, passing along _originalError_ if - it was given. - 1. Upon rejection of _p_ with reason _newError_, finalize with - _newError_. - * Shutdown: if any of the above requirements or steps ask to shutdown, optionally - with an error _error_, then: - 1. If _shuttingDown_ is *true*, abort these substeps. - 1. Set _shuttingDown_ to *true*. - 1. If _dest_.[[state]] is `"writable"` and ! WritableStreamCloseQueuedOrInFlight(_dest_) is *false*, - 1. If any chunks have been read but not yet written, write them to _dest_. - 1. Wait until every chunk that has been read has been written (i.e. the corresponding promises have - settled). - 1. Finalize, passing along _error_ if it was given. - * Finalize: both forms of shutdown will eventually ask to finalize, optionally with - an error _error_, which means to perform the following steps: - 1. Perform ! WritableStreamDefaultWriterRelease(_writer_). - 1. Perform ! ReadableStreamReaderGenericRelease(_reader_). - 1. If _signal_ is not *undefined*, remove _abortAlgorithm_ from _signal_. - 1. If _error_ was given, reject _promise_ with _error_. - 1. Otherwise, resolve _promise_ with *undefined*. - 1. Return _promise_. -
- -

- Various abstract operations performed here include object creation (often of promises), which usually would require - specifying a realm for the created object. However, because of the locking, none of these objects can be observed by - author code. As such, the realm used to create them does not matter. -

- -

The interface between readable streams and controllers

- -In terms of specification factoring, the way that the {{ReadableStream}} class encapsulates the behavior of -both simple readable streams and readable byte streams into a single class is by centralizing most of the -potentially-varying logic inside the two controller classes, {{ReadableStreamDefaultController}} and -{{ReadableByteStreamController}}. Those classes define most of the stateful internal slots and abstract -operations for how a stream's internal queue is managed and how it interfaces with its underlying source -or underlying byte source. - -Each controller class defines two internal methods, which are called by the {{ReadableStream}} algorithms: +
+
for await (const chunk of stream) { ... } +
for await (const chunk of stream.values({ {{ReadableStreamIteratorOptions/preventCancel}}: true })) { ... } +
+

Asynchronously iterates over the [=chunks=] in the stream's internal queue. -

-
\[[CancelSteps]](reason)
-
The controller's steps that run in reaction to the stream being canceled, - used to clean up the state stored in the controller and inform the underlying source.
+

Asynchronously iterating over the stream will [=locked to a reader|lock=] it, preventing any + other consumer from acquiring a reader. The lock will be released if the async iterator's + `return()` method is called, e.g. by `break`ing out of the loop. -

\[[PullSteps]]()
-
The controller's steps that run when a default reader is read from, used to pull from the controller any - queued chunks, or pull from the underlying source to get more chunks.
+

By default, calling the async iterator's `return()` method will also [=cancel a readable + stream|cancel=] the stream. To prevent this, use the stream's `values()` method, passing true for + the {{ReadableStreamIteratorOptions/preventCancel}} option. +

-(These are defined as internal methods, instead of as abstract operations, so that they can be called polymorphically by -the {{ReadableStream}} algorithms, without having to branch on which type of controller is present.) - -The rest of this section concerns abstract operations that go in the other direction: they are used by the controller -implementations to affect their associated {{ReadableStream}} object. This translates internal state changes of the -controller into developer-facing results visible through the {{ReadableStream}}'s public API. - -

ReadableStreamAddReadIntoRequest ( stream )

- - - 1. Assert: ! IsReadableStreamBYOBReader(_stream_.[[reader]]) is *true*. - 1. Assert: _stream_.[[state]] is `"readable"` or `"closed"`. - 1. Let _promise_ be a new promise. - 1. Let _readIntoRequest_ be Record {[[promise]]: _promise_}. - 1. Append _readIntoRequest_ as the last element of _stream_.[[reader]].[[readIntoRequests]]. - 1. Return _promise_. - - -

ReadableStreamAddReadRequest ( -stream )

- - - 1. Assert: ! IsReadableStreamDefaultReader(_stream_.[[reader]]) is *true*. - 1. Assert: _stream_.[[state]] is `"readable"`. - 1. Let _promise_ be a new promise. - 1. Let _readRequest_ be Record {[[promise]]: _promise_}. - 1. Append _readRequest_ as the last element of _stream_.[[reader]].[[readRequests]]. - 1. Return _promise_. - - -

ReadableStreamCancel ( stream, -reason )

- - - 1. Set _stream_.[[disturbed]] to *true*. - 1. If _stream_.[[state]] is `"closed"`, return a promise resolved with *undefined*. - 1. If _stream_.[[state]] is `"errored"`, return a promise rejected with _stream_.[[storedError]]. - 1. Perform ! ReadableStreamClose(_stream_). - 1. Let _sourceCancelPromise_ be ! _stream_.[[readableStreamController]].[[CancelSteps]](_reason_). - 1. Return the result of reacting to _sourceCancelPromise_ with a fulfillment step that returns *undefined*. - - -

ReadableStreamClose ( stream )

- - - 1. Assert: _stream_.[[state]] is `"readable"`. - 1. Set _stream_.[[state]] to `"closed"`. - 1. Let _reader_ be _stream_.[[reader]]. - 1. If _reader_ is *undefined*, return. - 1. If ! IsReadableStreamDefaultReader(_reader_) is *true*, - 1. Repeat for each _readRequest_ that is an element of _reader_.[[readRequests]], - 1. Resolve _readRequest_.[[promise]] with ! ReadableStreamCreateReadResult(*undefined*, *true*, - _reader_.[[forAuthorCode]]). - 1. Set _reader_.[[readRequests]] to an empty List. - 1. Resolve _reader_.[[closedPromise]] with *undefined*. - +
+ The [=asynchronous iterator initialization steps=] for a {{ReadableStream}}, given |stream|, + |iterator|, and |args|, are: -
- The case where stream.\[[state]] is "closed", but stream.\[[closeRequested]] is - false, will happen if the stream was closed without its controller's close method ever being - called: i.e., if the stream was closed by a call to {{ReadableStream/cancel(reason)}}. In this case we allow the - controller's close method to be called and silently do nothing, since the cancelation was outside the - control of the underlying source. + 1. Let |reader| be ? [$AcquireReadableStreamDefaultReader$](|stream|). + 1. Set |iterator|'s reader to |reader|. + 1. Let |preventCancel| be |args|[0]["{{ReadableStreamIteratorOptions/preventCancel}}"]. + 1. Set |iterator|'s prevent cancel to + |preventCancel|.
-

ReadableStreamCreateReadResult -( value, done, forAuthorCode )

- -
- When forAuthorCode is true, this abstract operation gives the same result - as CreateIterResultObject(value, done). This provides the expected semantics - when the object is to be returned from the {{ReadableStreamDefaultReader/read()|defaultReader.read()}} or - {{ReadableStreamBYOBReader/read()|byobReader.read()}} methods. - - However, resolving promises with such objects will unavoidably result in an access to - Object.prototype.then. For internal use, particularly in {{ReadableStream/pipeTo()}} and in other - specifications, it is important that reads not be observable by author code—even if that author code has tampered with - Object.prototype. For this reason, a false value of forAuthorCode results - in an object with a null prototype, keeping promise resolution unobservable. - - The underlying issue here is that reading from streams always uses promises for { value, done } objects, - even in specifications. Although it is conceivable we could rephrase all of the internal algorithms to not use - promises and not use JavaScript objects, and instead only package up the results into promise-for-{ value, done - } when a read() method is called, this would be a large undertaking, which we have not done. See - whatwg/infra#181 for more background on this subject. -
- - - 1. Let _prototype_ be *null*. - 1. If _forAuthorCode_ is *true*, set _prototype_ to %ObjectPrototype%. - 1. Assert: Type(_done_) is Boolean. - 1. Let _obj_ be ObjectCreate(_prototype_). - 1. Perform CreateDataProperty(_obj_, `"value"`, _value_). - 1. Perform CreateDataProperty(_obj_, `"done"`, _done_). - 1. Return _obj_. - - -

ReadableStreamError ( stream, e -)

- - - 1. Assert: ! IsReadableStream(_stream_) is *true*. - 1. Assert: _stream_.[[state]] is `"readable"`. - 1. Set _stream_.[[state]] to `"errored"`. - 1. Set _stream_.[[storedError]] to _e_. - 1. Let _reader_ be _stream_.[[reader]]. - 1. If _reader_ is *undefined*, return. - 1. If ! IsReadableStreamDefaultReader(_reader_) is *true*, - 1. Repeat for each _readRequest_ that is an element of _reader_.[[readRequests]], - 1. Reject _readRequest_.[[promise]] with _e_. - 1. Set _reader_.[[readRequests]] to a new empty List. - 1. Otherwise, - 1. Assert: ! IsReadableStreamBYOBReader(_reader_). - 1. Repeat for each _readIntoRequest_ that is an element of _reader_.[[readIntoRequests]], - 1. Reject _readIntoRequest_.[[promise]] with _e_. - 1. Set _reader_.[[readIntoRequests]] to a new empty List. - 1. Reject _reader_.[[closedPromise]] with _e_. - 1. Set _reader_.[[closedPromise]].[[PromiseIsHandled]] to *true*. - - -

ReadableStreamFulfillReadIntoRequest ( stream, chunk, done )

- - - 1. Let _reader_ be _stream_.[[reader]]. - 1. Let _readIntoRequest_ be the first element of _reader_.[[readIntoRequests]]. - 1. Remove _readIntoRequest_ from _reader_.[[readIntoRequests]], shifting all other elements downward (so that the - second becomes the first, and so on). - 1. Resolve _readIntoRequest_.[[promise]] with ! ReadableStreamCreateReadResult(_chunk_, _done_, - _reader_.[[forAuthorCode]]). - - -

ReadableStreamFulfillReadRequest ( stream, chunk, done )

- - - 1. Let _reader_ be _stream_.[[reader]]. - 1. Let _readRequest_ be the first element of _reader_.[[readRequests]]. - 1. Remove _readRequest_ from _reader_.[[readRequests]], shifting all other elements downward (so that the second - becomes the first, and so on). - 1. Resolve _readRequest_.[[promise]] with ! ReadableStreamCreateReadResult(_chunk_, _done_, - _reader_.[[forAuthorCode]]). - - -

ReadableStreamGetNumReadIntoRequests ( stream )

- - - 1. Return the number of elements in _stream_.[[reader]].[[readIntoRequests]]. - - -

ReadableStreamGetNumReadRequests ( stream )

- - - 1. Return the number of elements in _stream_.[[reader]].[[readRequests]]. - - -

ReadableStreamHasBYOBReader ( -stream )

- - - 1. Let _reader_ be _stream_.[[reader]]. - 1. If _reader_ is *undefined*, return *false*. - 1. If ! IsReadableStreamBYOBReader(_reader_) is *false*, return *false*. - 1. Return *true*. - - -

ReadableStreamHasDefaultReader ( -stream )

- - - 1. Let _reader_ be _stream_.[[reader]]. - 1. If _reader_ is *undefined*, return *false*. - 1. If ! IsReadableStreamDefaultReader(_reader_) is *false*, return *false*. - 1. Return *true*. - - -

Class -ReadableStreamDefaultReader

- -The {{ReadableStreamDefaultReader}} class represents a default reader designed to be vended by a -{{ReadableStream}} instance. +
+ The [=get the next iteration result=] steps for a {{ReadableStream}}, given stream and |iterator|, are: + + 1. Let |reader| be |iterator|'s [=ReadableStream async iterator/reader=]. + 1. If |reader|.\[[ownerReadableStream]] is undefined, return [=a promise rejected with=] a + {{TypeError}}. + 1. Return the result of [=reacting=] to ! [$ReadableStreamDefaultReaderRead$](|reader|) with the + following fulfillment steps given the argument |result|: + 1. Assert: [$Type$](|result|) is Object. + 1. Let |done| be ! [$Get$](|result|, "`done`"). + 1. Assert: [$Type$](|done|) is Boolean. + 1. If |done| is true: + 1. Perform ! [$ReadableStreamReaderGenericRelease$](|reader|). + 1. Return [=end of iteration=]. + 1. Let |value| be ! [$Get$](|result|, "`value`"). + 1. Return |value|. + + as well as the following rejection steps given the argument |reason|: + 1. Perform ! [$ReadableStreamReaderGenericRelease$](|reader|). + 1. Throw |reason|. +
-

Class definition

+
+ The [=asynchronous iterator return=] steps for a {{ReadableStream}}, given stream, |iterator|, and |arg|, are: + + 1. Let |reader| be |iterator|'s [=ReadableStream async iterator/reader=]. + 1. If |reader|.\[[ownerReadableStream]] is undefined, return [=a promise resolved with=] undefined. + 1. Assert: |reader|.\[[readRequests]] is [=list/is empty|empty=], as the async iterator machinery + guarantees that any previous calls to `next()` have settled before this is called. + 1. If |iterator|'s [=ReadableStream async iterator/prevent cancel=] is false: + 1. Let |result| be ! [$ReadableStreamReaderGenericCancel$](|reader|, |arg|). + 1. Perform ! [$ReadableStreamReaderGenericRelease$](|reader|). + 1. Return |result|. + 1. Perform ! [$ReadableStreamReaderGenericRelease$](|reader|). + 1. Return [=a promise resolved with=] undefined. +
-
+

The {{ReadableStreamDefaultReader}} class

-This section is non-normative. +The {{ReadableStreamDefaultReader}} class represents a [=default reader=] designed to be vended by a +{{ReadableStream}} instance. -If one were to write the {{ReadableStreamDefaultReader}} class in something close to the syntax of [[!ECMASCRIPT]], it -would look like +

Interface definition

-

-  class ReadableStreamDefaultReader {
-    constructor(stream)
+The Web IDL definition for the {{ReadableStreamDefaultReader}} class is given as follows:
 
-    get closed()
+
+[Exposed=(Window,Worker,Worklet)]
+interface ReadableStreamDefaultReader {
+  constructor(ReadableStream stream);
 
-    <a href="#default-reader-cancel">cancel</a>(reason)
-    <a href="#default-reader-read">read</a>()
-    <a href="#default-reader-release-lock">releaseLock</a>()
-  }
-</code></pre>
+  readonly attribute Promise<void> closed;
 
-</div>
+  Promise<void> cancel(optional any reason);
+  Promise<any> read();
+  void releaseLock();
+};
+
 
 

Internal slots

-Instances of {{ReadableStreamDefaultReader}} are created with the internal slots described in the following table: +Instances of {{ReadableStreamDefaultReader}} are created with the internal slots described in the +following table: - - - - - - + - + - + - + - + +
Internal SlotDescription (non-normative)
\[[closedPromise]] - A promise returned by the reader's {{ReadableStreamDefaultReader/closed}} getter -
Internal Slot + Description (non-normative) +
\[[forAuthorCode]] - A boolean flag indicating whether this reader is visible to author code -
\[[closedPromise]] + A promise returned by the reader's + {{ReadableStreamDefaultReader/closed}} getter
\[[ownerReadableStream]] - A {{ReadableStream}} instance that owns this reader -
\[[forAuthorCode]] + A boolean flag indicating whether this reader is visible to author code
\[[readRequests]] - A List of promises returned by calls to the reader's - {{ReadableStreamDefaultReader/read()}} method that have not yet been resolved, due to the consumer - requesting chunks sooner than they are available; also used for the IsReadableStreamDefaultReader brand check -
\[[ownerReadableStream]] + A {{ReadableStream}} instance that owns this reader +
\[[readRequests]] + A [=list=] of promises returned by calls to the reader's + {{ReadableStreamDefaultReader/read()}} method that have not yet been resolved, due to the + [=consumer=] requesting [=chunks=] sooner than they are available
-

new ReadableStreamDefaultReader(stream)

+

Constructor, methods, and properties

-
- The ReadableStreamDefaultReader constructor is generally not meant to be used directly; instead, a - stream's {{ReadableStream/getReader()}} method ought to be used. -
+
+
reader = new {{ReadableStreamDefaultReader(stream)|ReadableStreamDefaultReader}}(|stream|) +
+

This is equivalent to calling |stream|.{{ReadableStream/getReader()}}. - - 1. If ! IsReadableStream(_stream_) is *false*, throw a *TypeError* exception. - 1. If ! IsReadableStreamLocked(_stream_) is *true*, throw a *TypeError* exception. - 1. Perform ! ReadableStreamReaderGenericInitialize(*this*, _stream_). - 1. Set *this*.[[readRequests]] to a new empty List. - +

await reader.{{ReadableStreamDefaultReader/closed}} +
+

Returns a promise that will be fulfilled when the stream becomes closed, or rejected if the + stream ever errors or the reader's lock is [=release a read lock|released=] before the stream + finishes closing. -

Properties of the {{ReadableStreamDefaultReader}} prototype

+
await reader.{{ReadableStreamDefaultReader/cancel(reason)|cancel}}([ reason ]) +
+

If the reader is [=active reader|active=], behaves the same as + |stream|.{{ReadableStream/cancel(reason)|cancel}}(reason). -

get closed
+
{ value, done } = await reader.{{ReadableStreamDefaultReader/read()|read}}() +
+

Returns a promise that allows access to the next [=chunk=] from the stream's internal queue, if + available. -

- The closed getter returns a promise that will be fulfilled when the stream becomes closed, or rejected if - the stream ever errors or the reader's lock is released before the stream finishes - closing. -
+
    +
  • If the chunk does become available, the promise will be fulfilled with an object of the form + { value: theChunk, done: false }. - - 1. If ! IsReadableStreamDefaultReader(*this*) is *false*, return a promise rejected with a *TypeError* exception. - 1. Return *this*.[[closedPromise]]. - +
  • If the stream becomes closed, the promise will be fulfilled with an object of the form + { value: undefined, done: true }. -
    cancel(reason)
    +
  • If the stream becomes errored, the promise will be rejected with the relevant error. +
-
- If the reader is active, the cancel method behaves the same as that for the - associated stream. -
+

If reading a chunk causes the queue to become empty, more data will be pulled from the + [=underlying source=]. - - 1. If ! IsReadableStreamDefaultReader(*this*) is *false*, return a promise rejected with a *TypeError* exception. - 1. If *this*.[[ownerReadableStream]] is *undefined*, return a promise rejected with a *TypeError* exception. - 1. Return ! ReadableStreamReaderGenericCancel(*this*, _reason_). - +

reader.{{ReadableStreamDefaultReader/releaseLock()|releaseLock}}() +
+

[=release a read lock|Releases the reader's lock=] on the corresponding stream. After the lock + is released, the reader is no longer [=active reader|active=]. If the associated stream is errored + when the lock is released, the reader will appear errored in the same way from now on; otherwise, + the reader will appear closed. -

read()
+

A reader's lock cannot be released while it still has a pending read request, i.e., if a + promise returned by the reader's {{ReadableStreamDefaultReader/read()}} method has not yet been + settled. Attempting to do so will throw a {{TypeError}} and leave the reader locked to the stream. +

-
- The read method will return a promise that allows access to the next chunk from the stream's - internal queue, if available. - -
    -
  • If the chunk does become available, the promise will be fulfilled with an object of the form - { value: theChunk, done: false }. -
  • If the stream becomes closed, the promise will be fulfilled with an object of the form - { value: undefined, done: true }. -
  • If the stream becomes errored, the promise will be rejected with the relevant error. -
+
+ The ReadableStreamDefaultReader(|stream|) constructor steps + are: - If reading a chunk causes the queue to become empty, more data will be pulled from the underlying source. + 1. Perform ? [$SetUpReadableStreamDefaultReader$]([=this=], |stream|).
- - 1. If ! IsReadableStreamDefaultReader(*this*) is *false*, return a promise rejected with a *TypeError* exception. - 1. If *this*.[[ownerReadableStream]] is *undefined*, return a promise rejected with a *TypeError* exception. - 1. Return ! ReadableStreamDefaultReaderRead(*this*). - +
+ The closed + getter steps are: -
releaseLock()
+ 1. Return [=this=].\[[closedPromise]]. +
-
- The releaseLock method releases the reader's lock on the corresponding - stream. After the lock is released, the reader is no longer active. If the associated - stream is errored when the lock is released, the reader will appear errored in the same way from now on; otherwise, - the reader will appear closed. +
+ The cancel(|reason|) + method steps are: - A reader's lock cannot be released while it still has a pending read request, i.e., if a promise returned by the - reader's {{ReadableStreamDefaultReader/read()}} method has not yet been settled. Attempting to do so will throw - a TypeError and leave the reader locked to the stream. + 1. If [=this=].\[[ownerReadableStream]] is undefined, return [=a promise rejected with=] a + {{TypeError}} exception. + 1. Return ! [$ReadableStreamReaderGenericCancel$]([=this=], |reason|).
- - 1. If ! IsReadableStreamDefaultReader(*this*) is *false*, throw a *TypeError* exception. - 1. If *this*.[[ownerReadableStream]] is *undefined*, return. - 1. If *this*.[[readRequests]] is not empty, throw a *TypeError* exception. - 1. Perform ! ReadableStreamReaderGenericRelease(*this*). - +
+ The read() + method steps are: -

Class ReadableStreamBYOBReader

+ 1. If [=this=].\[[ownerReadableStream]] is undefined, return [=a promise rejected with=] a + {{TypeError}} exception. + 1. Return ! [$ReadableStreamDefaultReaderRead$]([=this=]). +
-The {{ReadableStreamBYOBReader}} class represents a BYOB reader designed to be vended by a {{ReadableStream}} -instance. +
+ The releaseLock() method steps are: -

Class definition

+ 1. If [=this=].\[[ownerReadableStream]] is undefined, return. + 1. If [=this=].\[[readRequests]] is not [=list/is empty|empty=], throw a {{TypeError}} exception. + 1. Perform ! [$ReadableStreamReaderGenericRelease$]([=this=]). +
-
+

The {{ReadableStreamBYOBReader}} class

-This section is non-normative. +The {{ReadableStreamDefaultReader}} class represents a [=BYOB reader=] designed to be vended by a +{{ReadableStream}} instance. -If one were to write the {{ReadableStreamBYOBReader}} class in something close to the syntax of [[!ECMASCRIPT]], it -would look like +

Interface definition

-

-  class ReadableStreamBYOBReader {
-    constructor(stream)
+The Web IDL definition for the {{ReadableStreamBYOBReader}} class is given as follows:
 
-    get closed()
+
+[Exposed=(Window,Worker,Worklet)]
+interface ReadableStreamBYOBReader {
+  constructor(ReadableStream stream);
 
-    <a href="#byob-reader-cancel">cancel</a>(reason)
-    <a href="#byob-reader-read">read</a>(view)
-    <a href="#byob-reader-release-lock">releaseLock</a>()
-  }
-</code></pre>
+  readonly attribute Promise<void> closed;
 
-</div>
+  Promise<void> cancel(optional any reason);
+  Promise<any> read(ArrayBufferView view);
+  void releaseLock();
+};
+
 
 

Internal slots

-Instances of {{ReadableStreamBYOBReader}} are created with the internal slots described in the following table: +Instances of {{ReadableStreamBYOBReader}} are created with the internal slots described in the +following table: - - - - - - + + + - + - + - + - +
Internal SlotDescription (non-normative)
Internal Slot + Description (non-normative) +
\[[closedPromise]] - A promise returned by the reader's {{ReadableStreamBYOBReader/closed}} getter -
\[[closedPromise]] + A promise returned by the reader's + {{ReadableStreamBYOBReader/closed}} getter
\[[forAuthorCode]] - A boolean flag indicating whether this reader is visible to author code -
\[[forAuthorCode]] + A boolean flag indicating whether this reader is visible to author code
\[[ownerReadableStream]] - A {{ReadableStream}} instance that owns this reader -
\[[ownerReadableStream]] + A {{ReadableStream}} instance that owns this reader
\[[readIntoRequests]] - A List of promises returned by calls to the reader's - {{ReadableStreamBYOBReader/read(view)}} method that have not yet been resolved, due to the consumer - requesting chunks sooner than they are available; also used for the IsReadableStreamBYOBReader brand check -
\[[readIntoRequests]] + A [=list=] of promises returned by calls to the reader's + {{ReadableStreamBYOBReader/read(view)}} method that have not yet been resolved, due to the + [=consumer=] requesting [=chunks=] sooner than they are available
-

new -ReadableStreamBYOBReader(stream)

- -
- The ReadableStreamBYOBReader constructor is generally not meant to be used directly; instead, a stream's - {{ReadableStream/getReader()}} method ought to be used. -
- - - 1. If ! IsReadableStream(_stream_) is *false*, throw a *TypeError* exception. - 1. If ! IsReadableByteStreamController(_stream_.[[readableStreamController]]) is *false*, throw a *TypeError* exception. - 1. If ! IsReadableStreamLocked(_stream_) is *true*, throw a *TypeError* exception. - 1. Perform ! ReadableStreamReaderGenericInitialize(*this*, _stream_). - 1. Set *this*.[[readIntoRequests]] to a new empty List. - +

Constructor, methods, and properties

-

Properties of the {{ReadableStreamBYOBReader}} prototype

+
+
reader = new {{ReadableStreamBYOBReader(stream)|ReadableStreamBYOBReader}}(|stream|) +
+

This is equivalent to calling |stream|.{{ReadableStream/getReader}}({ + {{ReadableStreamGetReaderOptions/mode}}: "{{ReadableStreamReaderMode/byob}}" }). + +

await reader.{{ReadableStreamBYOBReader/closed}} +
+

Returns a promise that will be fulfilled when the stream becomes closed, or rejected if the + stream ever errors or the reader's lock is [=release a read lock|released=] before the stream + finishes closing. + +

await reader.{{ReadableStreamBYOBReader/cancel(reason)|cancel}}([ reason ]) +
+

If the reader is [=active reader|active=], behaves the same + |stream|.{{ReadableStream/cancel(reason)|cancel}}(reason). + +

{ value, done } = await reader.{{ReadableStreamBYOBReader/read()|read}}(view) +
+

Attempts to reads bytes into |view|, and returns a promise resolved with the result: + +

    +
  • If the chunk does become available, the promise will be fulfilled with an object of the form + { value: theChunk, done: false }. In this case, |view| will be + [=ArrayBuffer/detached=] and no longer usable, but theChunk will be a new view (of + the same type) onto the same backing memory region, with the chunk's data written into it. + +
  • If the stream becomes closed, the promise will be fulfilled with an object of the form + { value: undefined, done: true }. + +
  • If the stream becomes errored, the promise will be rejected with the relevant error. +
-
get closed
+

If reading a chunk causes the queue to become empty, more data will be pulled from the + [=underlying source=]. -

- The closed getter returns a promise that will be fulfilled when the stream becomes closed, or rejected if - the stream ever errors or the reader's lock is released before the stream finishes - closing. -
+
reader.{{ReadableStreamBYOBReader/releaseLock()|releaseLock}}() +
+

[=release a read lock|Releases the reader's lock=] on the corresponding stream. After the lock + is released, the reader is no longer [=active reader|active=]. If the associated stream is errored + when the lock is released, the reader will appear errored in the same way from now on; otherwise, + the reader will appear closed. - - 1. If ! IsReadableStreamBYOBReader(*this*) is *false*, return a promise rejected with a *TypeError* exception. - 1. Return *this*.[[closedPromise]]. - +

A reader's lock cannot be released while it still has a pending read request, i.e., if a + promise returned by the reader's {{ReadableStreamBYOBReader/read()}} method has not yet been + settled. Attempting to do so will throw a {{TypeError}} and leave the reader locked to the stream. +

-
cancel(reason)
+
+ The ReadableStreamBYOBReader(|stream|) constructor steps + are: -
- If the reader is active, the cancel method behaves the same as that for the - associated stream. + 1. Perform ? [$SetUpReadableStreamBYOBReader$]([=this=], |stream|).
- - 1. If ! IsReadableStreamBYOBReader(*this*) is *false*, return a promise rejected with a *TypeError* exception. - 1. If *this*.[[ownerReadableStream]] is *undefined*, return a promise rejected with a *TypeError* exception. - 1. Return ! ReadableStreamReaderGenericCancel(*this*, _reason_). - +
+ The closed + getter steps are: -
read(view)
+ 1. Return [=this=].\[[closedPromise]]. +
-
- The read method will write read bytes into view and return a promise resolved with a - possibly transferred buffer as described below. - -
    -
  • If the chunk does become available, the promise will be fulfilled with an object of the form - { value: theChunk, done: false }. -
  • If the stream becomes closed, the promise will be fulfilled with an object of the form - { value: undefined, done: true }. -
  • If the stream becomes errored, the promise will be rejected with the relevant error. -
+
+ The cancel(|reason|) + method steps are: - If reading a chunk causes the queue to become empty, more data will be pulled from the underlying byte source. + 1. If [=this=].\[[ownerReadableStream]] is undefined, return [=a promise rejected with=] a + {{TypeError}} exception. + 1. Return ! [$ReadableStreamReaderGenericCancel$]([=this=], |reason|).
- - 1. If ! IsReadableStreamBYOBReader(*this*) is *false*, return a promise rejected with a *TypeError* exception. - 1. If *this*.[[ownerReadableStream]] is *undefined*, return a promise rejected with a *TypeError* exception. - 1. If Type(_view_) is not Object, return a promise rejected with a *TypeError* exception. - 1. If _view_ does not have a [[ViewedArrayBuffer]] internal slot, return a promise rejected with a *TypeError* - exception. - 1. If ! IsDetachedBuffer(_view_.[[ViewedArrayBuffer]]) is *true*, return a promise rejected with a *TypeError* - exception. - 1. If _view_.[[ByteLength]] is *0*, return a promise rejected with a *TypeError* exception. - 1. Return ! ReadableStreamBYOBReaderRead(*this*, _view_). - +
+ The read(|view|) + method steps are: -
releaseLock()
- -
- The releaseLock method releases the reader's lock on the corresponding - stream. After the lock is released, the reader is no longer active. If the associated - stream is errored when the lock is released, the reader will appear errored in the same way from now on; otherwise, - the reader will appear closed. + 1. If |view|.\[[ByteLength]] is 0, return [=a promise rejected with=] a {{TypeError}} exception. + 1. If |view|.\[[ViewedArrayBuffer]].\[[ArrayBufferByteLength]] is 0, return [=a promise rejected + with=] a {{TypeError}} exception. + 1. If [=this=].\[[ownerReadableStream]] is undefined, return [=a promise rejected with=] a + {{TypeError}} exception. + 1. Return ! [$ReadableStreamBYOBReaderRead$]([=this=], |view|). +
- A reader's lock cannot be released while it still has a pending read request, i.e., if a promise returned by the - reader's {{ReadableStreamBYOBReader/read()}} method has not yet been settled. Attempting to do so will throw - a TypeError and leave the reader locked to the stream. -
- - - 1. If ! IsReadableStreamBYOBReader(*this*) is *false*, throw a *TypeError* exception. - 1. If *this*.[[ownerReadableStream]] is *undefined*, return. - 1. If *this*.[[readIntoRequests]] is not empty, throw a *TypeError* exception. - 1. Perform ! ReadableStreamReaderGenericRelease(*this*). - - -

Readable stream reader abstract operations

- -

IsReadableStreamDefaultReader ( -x )

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

IsReadableStreamBYOBReader ( -x )

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

ReadableStreamReaderGenericCancel ( reader, reason )

- - - 1. Let _stream_ be _reader_.[[ownerReadableStream]]. - 1. Assert: _stream_ is not *undefined*. - 1. Return ! ReadableStreamCancel(_stream_, _reason_). - - -

ReadableStreamReaderGenericInitialize ( reader, stream )

- - - 1. Set _reader_.[[forAuthorCode]] to *true*. - 1. Set _reader_.[[ownerReadableStream]] to _stream_. - 1. Set _stream_.[[reader]] to _reader_. - 1. If _stream_.[[state]] is `"readable"`, - 1. Set _reader_.[[closedPromise]] to a new promise. - 1. Otherwise, if _stream_.[[state]] is `"closed"`, - 1. Set _reader_.[[closedPromise]] to a promise resolved with *undefined*. - 1. Otherwise, - 1. Assert: _stream_.[[state]] is `"errored"`. - 1. Set _reader_.[[closedPromise]] to a promise rejected with _stream_.[[storedError]]. - 1. Set _reader_.[[closedPromise]].[[PromiseIsHandled]] to *true*. - - -

ReadableStreamReaderGenericRelease ( reader )

- - - 1. Assert: _reader_.[[ownerReadableStream]] is not *undefined*. - 1. Assert: _reader_.[[ownerReadableStream]].[[reader]] is _reader_. - 1. If _reader_.[[ownerReadableStream]].[[state]] is `"readable"`, reject _reader_.[[closedPromise]] with a *TypeError* - exception. - 1. Otherwise, set _reader_.[[closedPromise]] to a promise rejected with a *TypeError* exception. - 1. Set _reader_.[[closedPromise]].[[PromiseIsHandled]] to *true*. - 1. Set _reader_.[[ownerReadableStream]].[[reader]] to *undefined*. - 1. Set _reader_.[[ownerReadableStream]] to *undefined*. - - -

ReadableStreamBYOBReaderRead -( reader, view )

- - - 1. Let _stream_ be _reader_.[[ownerReadableStream]]. - 1. Assert: _stream_ is not *undefined*. - 1. Set _stream_.[[disturbed]] to *true*. - 1. If _stream_.[[state]] is `"errored"`, return a promise rejected with _stream_.[[storedError]]. - 1. Return ! ReadableByteStreamControllerPullInto(_stream_.[[readableStreamController]], _view_). - - -

ReadableStreamDefaultReaderRead ( reader )

- - - 1. Let _stream_ be _reader_.[[ownerReadableStream]]. - 1. Assert: _stream_ is not *undefined*. - 1. Set _stream_.[[disturbed]] to *true*. - 1. If _stream_.[[state]] is `"closed"`, return a promise resolved with ! - ReadableStreamCreateReadResult(*undefined*, *true*, _reader_.[[forAuthorCode]]). - 1. If _stream_.[[state]] is `"errored"`, return a promise rejected with _stream_.[[storedError]]. - 1. Assert: _stream_.[[state]] is `"readable"`. - 1. Return ! _stream_.[[readableStreamController]].[[PullSteps]](). - - -

Class -ReadableStreamDefaultController

- -The {{ReadableStreamDefaultController}} class has methods that allow control of a {{ReadableStream}}'s state and -internal queue. When constructing a {{ReadableStream}} that is not a readable byte stream, the -underlying source is given a corresponding {{ReadableStreamDefaultController}} instance to manipulate. - -

Class definition

+
+ The releaseLock() method steps are: -
+ 1. If [=this=].\[[ownerReadableStream]] is undefined, return. + 1. If [=this=].\[[readIntoRequests]] is not [=list/is empty|empty=], throw a {{TypeError}} + exception. + 1. Perform ! [$ReadableStreamReaderGenericRelease$]([=this=]). +
-This section is non-normative. +

The {{ReadableStreamDefaultController}} class

-If one were to write the {{ReadableStreamDefaultController}} class in something close to the syntax of [[!ECMASCRIPT]], -it would look like +The {{ReadableStreamDefaultController}} class has methods that allow control of a +{{ReadableStream}}'s state and [=internal queue=]. When constructing a {{ReadableStream}} that is +not a [=readable byte stream=], the [=underlying source=] is given a corresponding +{{ReadableStreamDefaultController}} instance to manipulate. -

-  class ReadableStreamDefaultController {
-    constructor() // always throws
+

Interface definition

- get desiredSize() +The Web IDL definition for the {{ReadableStreamDefaultController}} class is given as follows: - close() - enqueue(chunk) - error(e) - } -
+ +[Exposed=(Window,Worker,Worklet)] +interface ReadableStreamDefaultController { + readonly attribute unrestricted double? desiredSize; -</div> + void close(); + void enqueue(optional any chunk); + void error(optional any e); +}; +

Internal slots

-Instances of {{ReadableStreamDefaultController}} are created with the internal slots described in the following table: +Instances of {{ReadableStreamDefaultController}} are created with the internal slots described in +the following table: - - - - - - + - + + + - + - + - + - + - + - + - + - + - + - + +
Internal SlotDescription (non-normative)
\[[cancelAlgorithm]] - A promise-returning algorithm, taking one argument (the cancel reason), which communicates - a requested cancelation to the underlying source -
Internal SlotDescription (non-normative)
\[[closeRequested]] - A boolean flag indicating whether the stream has been closed by its underlying - source, but still has chunks in its internal queue that have not yet been read -
\[[cancelAlgorithm]] + A promise-returning algorithm, taking one argument (the cancel reason), + which communicates a requested cancelation to the [=underlying source=]
\[[controlledReadableStream]] - The {{ReadableStream}} instance controlled -
\[[closeRequested]] + A boolean flag indicating whether the stream has been closed by its + [=underlying source=], but still has [=chunks=] in its internal queue that have not yet been + read
\[[pullAgain]] - A boolean flag set to true if the stream's mechanisms requested a call - to the underlying source's pull algorithm to pull more data, but the pull could not yet be done since a - previous call is still executing -
\[[controlledReadableStream]] + The {{ReadableStream}} instance controlled
\[[pullAlgorithm]] - A promise-returning algorithm that pulls data from the underlying source -
\[[pullAgain]] + A boolean flag set to true if the stream's mechanisms requested a call + to the [=underlying source=]'s pull algorithm to pull more data, but the pull could not yet be + done since a previous call is still executing
\[[pulling]] - A boolean flag set to true while the underlying source's - pull algorithm is executing and the returned promise has not yet fulfilled, used to prevent reentrant calls -
\[[pullAlgorithm]] + A promise-returning algorithm that pulls data from the [=underlying + source=]
\[[queue]] - A List representing the stream's internal queue of chunks -
\[[pulling]] + A boolean flag set to true while the [=underlying source=]'s pull + algorithm is executing and the returned promise has not yet fulfilled, used to prevent reentrant + calls
\[[queueTotalSize]] - The total size of all the chunks stored in \[[queue]] (see [[#queue-with-sizes]]) -
\[[queue]] + A [=list=] representing the stream's internal queue of [=chunks=]
\[[started]] - A boolean flag indicating whether the underlying source has finished starting -
\[[queueTotalSize]] + The total size of all the chunks stored in \[[queue]] (see + [[#queue-with-sizes]])
\[[strategyHWM]] - A number supplied to the constructor as part of the stream's queuing strategy, - indicating the point at which the stream will apply backpressure to its underlying source -
\[[started]] + A boolean flag indicating whether the [=underlying source=] has + finished starting
\[[strategySizeAlgorithm]] - An algorithm to calculate the size of enqueued chunks, as part of the stream's - queuing strategy -
\[[strategyHWM]] + A number supplied to the constructor as part of the stream's [=queuing + strategy=], indicating the point at which the stream will apply [=backpressure=] to its + [=underlying source=] +
\[[strategySizeAlgorithm]] + An algorithm to calculate the size of enqueued [=chunks=], as part of + the stream's [=queuing strategy=]
-

new ReadableStreamDefaultController()

- -
- The ReadableStreamDefaultController constructor cannot be used directly; - {{ReadableStreamDefaultController}} instances are created automatically during {{ReadableStream}} construction. -
+

Methods and properties

+ +
+
desiredSize = controller.{{ReadableStreamDefaultController/desiredSize}} +
+

Returns the [=desired size to fill a stream's internal queue|desired size to fill the + controlled stream's internal queue=]. It can be negative, if the queue is over-full. An + [=underlying source=] ought to use this information to determine when and how to apply + [=backpressure=]. + +

controller.{{ReadableStreamDefaultController/close()|close}}() +
+

Closes the controlled readable stream. [=Consumers=] will still be able to read any + previously-enqueued [=chunks=] from the stream, but once those are read, the stream will become + closed. + +

controller.{{ReadableStreamDefaultController/enqueue()|enqueue}}(chunk) +
+

Enqueues the given [=chunk=] chunk in the controlled readable stream. + +

controller.{{ReadableStreamDefaultController/error()|error}}(e) +
+

Errors the controlled readable stream, making all future interactions with it fail with the + given error e. +

- - 1. Throw a *TypeError*. - +
+ The desiredSize attribute's getter steps are: -

Properties of the {{ReadableStreamDefaultController}} prototype

+ 1. Return ! [$ReadableStreamDefaultControllerGetDesiredSize$]([=this=]). +
-
get -desiredSize
+
+ The close() method steps are: -
- The desiredSize getter returns the desired size - to fill the controlled stream's internal queue. It can be negative, if the queue is over-full. An underlying - source ought to use this information to determine when and how to apply backpressure. + 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$]([=this=]) is false, throw a + {{TypeError}} exception. + 1. Perform ! [$ReadableStreamDefaultControllerClose$]([=this=]).
- - 1. If ! IsReadableStreamDefaultController(*this*) is *false*, throw a *TypeError* exception. - 1. Return ! ReadableStreamDefaultControllerGetDesiredSize(*this*). - - -
close()
+
+ The enqueue(|chunk|) method steps are: -
- The close method will close the controlled readable stream. Consumers will still be able to read - any previously-enqueued chunks from the stream, but once those are read, the stream will become closed. + 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$]([=this=]) is false, throw a + {{TypeError}} exception. + 1. Perform ? [$ReadableStreamDefaultControllerEnqueue$]([=this=], |chunk|).
- - 1. If ! IsReadableStreamDefaultController(*this*) is *false*, throw a *TypeError* exception. - 1. If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(*this*) is *false*, throw a *TypeError* exception. - 1. Perform ! ReadableStreamDefaultControllerClose(*this*). - - -
enqueue(chunk)
+
+ The error(|e|) method steps are: -
- The enqueue method will enqueue a given chunk in the controlled readable stream. + 1. Perform ! [$ReadableStreamDefaultControllerError$]([=this=], |e|).
- - 1. If ! IsReadableStreamDefaultController(*this*) is *false*, throw a *TypeError* exception. - 1. If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(*this*) is *false*, throw a *TypeError* exception. - 1. Return ? ReadableStreamDefaultControllerEnqueue(*this*, _chunk_). - +

Internal methods

-
error(e)
+The following are internal methods implemented by each {{ReadableStreamDefaultController}} instance. +The readable stream implementation will polymorphically call to either these, or to their +counterparts for BYOB controllers, as discussed in [[#rs-abstract-ops-used-by-controllers]]. -
- The error method will error the readable stream, making all future interactions with it fail with the - given error e. -
- - - 1. If ! IsReadableStreamDefaultController(*this*) is *false*, throw a *TypeError* exception. - 1. Perform ! ReadableStreamDefaultControllerError(*this*, _e_). - - -

Readable stream default controller internal methods

- -The following are additional internal methods implemented by each {{ReadableStreamDefaultController}} instance. The -readable stream implementation will polymorphically call to either these or their counterparts for BYOB controllers. - -
\[[CancelSteps]](reason)
- - - 1. Perform ! ResetQueue(*this*). - 1. Let _result_ be the result of performing *this*.[[cancelAlgorithm]], passing _reason_. - 1. Perform ! ReadableStreamDefaultControllerClearAlgorithms(*this*). - 1. Return _result_. - - -
\[[PullSteps]]( )
- - - 1. Let _stream_ be *this*.[[controlledReadableStream]]. - 1. If *this*.[[queue]] is not empty, - 1. Let _chunk_ be ! DequeueValue(*this*). - 1. If *this*.[[closeRequested]] is *true* and *this*.[[queue]] is empty, - 1. Perform ! ReadableStreamDefaultControllerClearAlgorithms(*this*). - 1. Perform ! ReadableStreamClose(_stream_). - 1. Otherwise, perform ! ReadableStreamDefaultControllerCallPullIfNeeded(*this*). - 1. Return a promise resolved with ! ReadableStreamCreateReadResult(_chunk_, *false*, - _stream_.[[reader]].[[forAuthorCode]]). - 1. Let _pendingPromise_ be ! ReadableStreamAddReadRequest(_stream_). - 1. Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(*this*). - 1. Return _pendingPromise_. - - -

Readable stream default controller abstract operations

- -

IsReadableStreamDefaultController ( x )

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

ReadableStreamDefaultControllerCallPullIfNeeded ( controller )

- - - 1. Let _shouldPull_ be ! ReadableStreamDefaultControllerShouldCallPull(_controller_). - 1. If _shouldPull_ is *false*, return. - 1. If _controller_.[[pulling]] is *true*, - 1. Set _controller_.[[pullAgain]] to *true*. - 1. Return. - 1. Assert: _controller_.[[pullAgain]] is *false*. - 1. Set _controller_.[[pulling]] to *true*. - 1. Let _pullPromise_ be the result of performing _controller_.[[pullAlgorithm]]. - 1. Upon fulfillment of _pullPromise_, - 1. Set _controller_.[[pulling]] to *false*. - 1. If _controller_.[[pullAgain]] is *true*, - 1. Set _controller_.[[pullAgain]] to *false*. - 1. Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(_controller_). - 1. Upon rejection of _pullPromise_ with reason _e_, - 1. Perform ! ReadableStreamDefaultControllerError(_controller_, _e_). - - -

ReadableStreamDefaultControllerShouldCallPull ( controller )

- - - 1. Let _stream_ be _controller_.[[controlledReadableStream]]. - 1. If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(_controller_) is *false*, return *false*. - 1. If _controller_.[[started]] is *false*, return *false*. - 1. If ! IsReadableStreamLocked(_stream_) is *true* and ! ReadableStreamGetNumReadRequests(_stream_) > *0*, return - *true*. - 1. Let _desiredSize_ be ! ReadableStreamDefaultControllerGetDesiredSize(_controller_). - 1. Assert: _desiredSize_ is not *null*. - 1. If _desiredSize_ > *0*, return *true*. - 1. Return *false*. - - -

ReadableStreamDefaultControllerClearAlgorithms ( controller )

- -This abstract operation is called once the stream is closed or errored and the algorithms will not be executed any more. -By removing the algorithm references it permits the underlying source object to be garbage collected even if the -{{ReadableStream}} itself is still referenced. - -

The results of this algorithm are not currently observable, but could become so if JavaScript eventually -adds weak references. But even without that factor, -implementations will likely want to include similar steps.

- - - 1. Set _controller_.[[pullAlgorithm]] to *undefined*. - 1. Set _controller_.[[cancelAlgorithm]] to *undefined*. - 1. Set _controller_.[[strategySizeAlgorithm]] to *undefined*. - - -

ReadableStreamDefaultControllerClose ( controller )

- -This abstract operation can be called by other specifications that wish to close a readable stream, in the same way -a developer-created stream would be closed by its associated controller object. Specifications should not do -this to streams they did not create. - - - 1. If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(_controller_) is *false*, return. - 1. Let _stream_ be _controller_.[[controlledReadableStream]]. - 1. Set _controller_.[[closeRequested]] to *true*. - 1. If _controller_.[[queue]] is empty, - 1. Perform ! ReadableStreamDefaultControllerClearAlgorithms(_controller_). - 1. Perform ! ReadableStreamClose(_stream_). - - -

ReadableStreamDefaultControllerEnqueue ( controller, chunk )

- -This abstract operation can be called by other specifications that wish to enqueue chunks in a readable stream, -in the same way a developer would enqueue chunks using the stream's associated controller object. Specifications should -not do this to streams they did not create. - - - 1. If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(_controller_) is *false*, return. - 1. Let _stream_ be _controller_.[[controlledReadableStream]]. - 1. If ! IsReadableStreamLocked(_stream_) is *true* and ! ReadableStreamGetNumReadRequests(_stream_) > *0*, perform - ! ReadableStreamFulfillReadRequest(_stream_, _chunk_, *false*). - 1. Otherwise, - 1. Let _result_ be the result of performing _controller_.[[strategySizeAlgorithm]], passing in _chunk_, and - interpreting the result as an ECMAScript completion value. - 1. If _result_ is an abrupt completion, - 1. Perform ! ReadableStreamDefaultControllerError(_controller_, _result_.[[Value]]). - 1. Return _result_. - 1. Let _chunkSize_ be _result_.[[Value]]. - 1. Let _enqueueResult_ be EnqueueValueWithSize(_controller_, _chunk_, _chunkSize_). - 1. If _enqueueResult_ is an abrupt completion, - 1. Perform ! ReadableStreamDefaultControllerError(_controller_, _enqueueResult_.[[Value]]). - 1. Return _enqueueResult_. - 1. Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(_controller_). - - -

ReadableStreamDefaultControllerError ( controller, e )

- -This abstract operation can be called by other specifications that wish to move a readable stream to an errored state, -in the same way a developer would error a stream using its associated controller object. Specifications should -not do this to streams they did not create. - - - 1. Let _stream_ be _controller_.[[controlledReadableStream]]. - 1. If _stream_.[[state]] is not `"readable"`, return. - 1. Perform ! ResetQueue(_controller_). - 1. Perform ! ReadableStreamDefaultControllerClearAlgorithms(_controller_). - 1. Perform ! ReadableStreamError(_stream_, _e_). - - -

ReadableStreamDefaultControllerGetDesiredSize ( controller )

- -This abstract operation can be called by other specifications that wish to determine the desired size to fill this stream's internal queue, similar to how a developer would consult -the {{ReadableStreamDefaultController/desiredSize}} property of the stream's associated controller object. -Specifications should not use this on streams they did not create. - - - 1. Let _stream_ be _controller_.[[controlledReadableStream]]. - 1. Let _state_ be _stream_.[[state]]. - 1. If _state_ is `"errored"`, return *null*. - 1. If _state_ is `"closed"`, return *0*. - 1. Return _controller_.[[strategyHWM]] − _controller_.[[queueTotalSize]]. - - -

ReadableStreamDefaultControllerHasBackpressure ( controller )

- -This abstract operation is used in the implementation of TransformStream. - - - 1. If ! ReadableStreamDefaultControllerShouldCallPull(_controller_) is *true*, return *false*. - 1. Otherwise, return *true*. - - -

ReadableStreamDefaultControllerCanCloseOrEnqueue ( controller )

- - - 1. Let _state_ be _controller_.[[controlledReadableStream]].[[state]]. - 1. If _controller_.[[closeRequested]] is *false* and _state_ is `"readable"`, return *true*. - 1. Otherwise, return *false*. - +
+ \[[CancelSteps]](|reason|) implements the + [$ReadableStreamController/[[CancelSteps]]$] contract. It performs the following steps: -
- The case where stream.\[[closeRequested]] is false, but stream.\[[state]] is - not "readable", happens when the stream is errored via {{ReadableStreamDefaultController/error(e)}}, or - when it is closed without its controller's close method ever being called: e.g., if the stream was closed - by a call to {{ReadableStream/cancel(reason)}}. -
- -

SetUpReadableStreamDefaultController(stream, controller, startAlgorithm, -pullAlgorithm, cancelAlgorithm, highWaterMark, sizeAlgorithm )

- - - 1. Assert: _stream_.[[readableStreamController]] is *undefined*. - 1. Set _controller_.[[controlledReadableStream]] to _stream_. - 1. Set _controller_.[[queue]] and _controller_.[[queueTotalSize]] to *undefined*, then perform ! - ResetQueue(_controller_). - 1. Set _controller_.[[started]], _controller_.[[closeRequested]], _controller_.[[pullAgain]], and - _controller_.[[pulling]] to *false*. - 1. Set _controller_.[[strategySizeAlgorithm]] to _sizeAlgorithm_ and _controller_.[[strategyHWM]] to _highWaterMark_. - 1. Set _controller_.[[pullAlgorithm]] to _pullAlgorithm_. - 1. Set _controller_.[[cancelAlgorithm]] to _cancelAlgorithm_. - 1. Set _stream_.[[readableStreamController]] to _controller_. - 1. Let _startResult_ be the result of performing _startAlgorithm_. (This may throw an exception.) - 1. Let _startPromise_ be a promise resolved with _startResult_. - 1. Upon fulfillment of _startPromise_, - 1. Set _controller_.[[started]] to *true*. - 1. Assert: _controller_.[[pulling]] is *false*. - 1. Assert: _controller_.[[pullAgain]] is *false*. - 1. Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(_controller_). - 1. Upon rejection of _startPromise_ with reason _r_, - 1. Perform ! ReadableStreamDefaultControllerError(_controller_, _r_). - - -

SetUpReadableStreamDefaultControllerFromUnderlyingSource(stream, underlyingSource, -highWaterMark, sizeAlgorithm )

- - - 1. Assert: _underlyingSource_ is not *undefined*. - 1. Let _controller_ be ObjectCreate(the original value of `ReadableStreamDefaultController`'s `prototype` - property). - 1. Let _startAlgorithm_ be the following steps: - 1. Return ? InvokeOrNoop(_underlyingSource_, `"start"`, « _controller_ »). - 1. Let _pullAlgorithm_ be ? CreateAlgorithmFromUnderlyingMethod(_underlyingSource_, `"pull"`, *0*, « _controller_ »). - 1. Let _cancelAlgorithm_ be ? CreateAlgorithmFromUnderlyingMethod(_underlyingSource_, `"cancel"`, *1*, « »). - 1. Perform ? SetUpReadableStreamDefaultController(_stream_, _controller_, _startAlgorithm_, _pullAlgorithm_, - _cancelAlgorithm_, _highWaterMark_, _sizeAlgorithm_). - - -

Class -ReadableByteStreamController

- -The {{ReadableByteStreamController}} class has methods that allow control of a {{ReadableStream}}'s state and -internal queue. When constructing a {{ReadableStream}}, the underlying byte source is given a -corresponding {{ReadableByteStreamController}} instance to manipulate. - -

Class definition

+ 1. Perform ! [$ResetQueue$]([=this=]). + 1. Let |result| be the result of performing [=this=].\[[cancelAlgorithm]], passing |reason|. + 1. Perform ! [$ReadableStreamDefaultControllerClearAlgorithms$]([=this=]). + 1. Return |result|. +
-
+
+ \[[PullSteps]]() implements the + [$ReadableStreamController/[[PullSteps]]$] contract. It performs the following steps: + + 1. Let |stream| be [=this=].\[[controlledReadableStream]]. + 1. If [=this=].\[[queue]] is not [=list/is empty|empty=], + 1. Let |chunk| be ! [$DequeueValue$]([=this=]). + 1. If [=this=].\[[closeRequested]] is true and [=this=].\[[queue]] [=list/is empty=], + 1. Perform ! [$ReadableStreamDefaultControllerClearAlgorithms$]([=this=]). + 1. Perform ! [$ReadableStreamClose$](|stream|). + 1. Otherwise, perform ! [$ReadableStreamDefaultControllerCallPullIfNeeded$]([=this=]). + 1. Return [=a promise resolved with=] ! [$ReadableStreamCreateReadResult$](|chunk|, false, + |stream|.\[[reader]].\[[forAuthorCode]]). + 1. Let |pendingPromise| be ! [$ReadableStreamAddReadRequest$](|stream|). + 1. Perform ! [$ReadableStreamDefaultControllerCallPullIfNeeded$]([=this=]). + 1. Return |pendingPromise|. +
-This section is non-normative. +

The {{ReadableByteStreamController}} class

-If one were to write the {{ReadableByteStreamController}} class in something close to the syntax of [[!ECMASCRIPT]], it -would look like +The {{ReadableByteStreamController}} class has methods that allow control of a {{ReadableStream}}'s +state and [=internal queue=]. When constructing a {{ReadableStream}} that is a [=readable byte +stream=], the [=underlying source=] is given a corresponding {{ReadableByteStreamController}} +instance to manipulate. -

-  class ReadableByteStreamController {
-    constructor() // always throws
+

Interface definition

- get byobRequest() - get desiredSize() +The Web IDL definition for the {{ReadableByteStreamController}} class is given as follows: - close() - enqueue(chunk) - error(e) - } -
+ +[Exposed=(Window,Worker,Worklet)] +interface ReadableByteStreamController { + readonly attribute ReadableStreamBYOBRequest? byobRequest; + readonly attribute unrestricted double? desiredSize; -</div> + void close(); + void enqueue(ArrayBufferView chunk); + void error(optional any e); +}; +

Internal slots

-Instances of {{ReadableByteStreamController}} are created with the internal slots described in the following table: +Instances of {{ReadableByteStreamController}} are created with the internal slots described in the +following table: - - - - - - + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - +
Internal SlotDescription (non-normative)
Internal SlotDescription (non-normative)
\[[autoAllocateChunkSize]] - A positive integer, when the automatic buffer allocation feature is enabled. In that case, - this value specifies the size of buffer to allocate. It is undefined otherwise. -
\[[autoAllocateChunkSize]] + A positive integer, when the automatic buffer allocation feature is + enabled. In that case, this value specifies the size of buffer to allocate. It is undefined + otherwise.
\[[byobRequest]] - A {{ReadableStreamBYOBRequest}} instance representing the current BYOB pull request, - or undefined if there are no pending requests -
\[[byobRequest]] + A {{ReadableStreamBYOBRequest}} instance representing the current BYOB + pull request, or null if there are no pending requests
\[[cancelAlgorithm]] - A promise-returning algorithm, taking one argument (the cancel reason), which communicates - a requested cancelation to the underlying source -
\[[cancelAlgorithm]] + A promise-returning algorithm, taking one argument (the cancel reason), + which communicates a requested cancelation to the [=underlying byte source=]
\[[closeRequested]] - A boolean flag indicating whether the stream has been closed by its underlying byte - source, but still has chunks in its internal queue that have not yet been read -
\[[closeRequested]] + A boolean flag indicating whether the stream has been closed by its + [=underlying byte source=], but still has [=chunks=] in its internal queue that have not yet been + read
\[[controlledReadableByteStream]] - The {{ReadableStream}} instance controlled -
\[[controlledReadableStream]] + The {{ReadableStream}} instance controlled
\[[pullAgain]] - A boolean flag set to true if the stream's mechanisms requested a call - to the underlying byte source's {{underlying source/pull()}} method to pull more data, but the pull could - not yet be done since a previous call is still executing -
\[[pullAgain]] + A boolean flag set to true if the stream's mechanisms requested a call + to the [=underlying byte source=]'s pull algorithm to pull more data, but the pull could not yet + be done since a previous call is still executing
\[[pullAlgorithm]] - A promise-returning algorithm that pulls data from the underlying source -
\[[pullAlgorithm]] + A promise-returning algorithm that pulls data from the [=underlying + byte source=]
\[[pulling]] - A boolean flag set to true while the underlying byte source's - {{underlying source/pull()}} method is executing and has not yet fulfilled, used to prevent reentrant calls -
\[[pulling]] + A boolean flag set to true while the [=underlying byte source=]'s pull + algorithm is executing and the returned promise has not yet fulfilled, used to prevent reentrant + calls
\[[pendingPullIntos]] - A List of descriptors representing pending BYOB pull requests -
\[[pendingPullIntos]] + A [=list=] of descriptors representing pending BYOB pull requests
\[[queue]] - A List representing the stream's internal queue of chunks -
\[[queue]] + A [=list=] representing the stream's internal queue of [=chunks=]
\[[queueTotalSize]] - The total size (in bytes) of all the chunks stored in \[[queue]] -
\[[queueTotalSize]] + The total size, in bytes, of all the chunks stored in \[[queue]] (see + [[#queue-with-sizes]])
\[[started]] - A boolean flag indicating whether the underlying source has finished starting -
\[[started]] + A boolean flag indicating whether the [=underlying byte source=] has + finished starting
\[[strategyHWM]] - A number supplied to the constructor as part of the stream's queuing strategy, - indicating the point at which the stream will apply backpressure to its underlying byte source -
\[[strategyHWM]] + A number supplied to the constructor as part of the stream's [=queuing + strategy=], indicating the point at which the stream will apply [=backpressure=] to its + [=underlying byte source=]
-

Although {{ReadableByteStreamController}} instances have \[[queue]] and \[[queueTotalSize]] slots, we do not use - most of the abstract operations in [[#queue-with-sizes]] on them, as the way in which we manipulate this queue is - rather different than the others in the spec. Instead, we update the two slots together manually.

+

Although {{ReadableByteStreamController}} instances have \[[queue]] and \[[queueTotalSize]] + slots, we do not use most of the abstract operations in [[#queue-with-sizes]] on them, as the way + in which we manipulate this queue is rather different than the others in the spec. Instead, we + update the two slots together manually. -

This might be cleaned up in a future spec refactoring.

+

This might be cleaned up in a future spec refactoring.

-

new -ReadableByteStreamController()

+

Methods and properties

+ +
+
byobRequest = controller.{{ReadableByteStreamController/byobRequest}} +
+

Returns the current BYOB pull request, or null if there isn't one. + +

desiredSize = controller.{{ReadableByteStreamController/desiredSize}} +
+

Returns the [=desired size to fill a stream's internal queue|desired size to fill the + controlled stream's internal queue=]. It can be negative, if the queue is over-full. An + [=underlying byte source=] ought to use this information to determine when and how to apply + [=backpressure=]. + +

controller.{{ReadableByteStreamController/close()|close}}() +
+

Closes the controlled readable stream. [=Consumers=] will still be able to read any + previously-enqueued [=chunks=] from the stream, but once those are read, the stream will become + closed. + +

controller.{{ReadableByteStreamController/enqueue()|enqueue}}(chunk) +
+

Enqueues the given [=chunk=] chunk in the controlled readable stream. The + chunk has to be an {{ArrayBufferView}} instance, or else a {{TypeError}} will be thrown. + +

controller.{{ReadableByteStreamController/error()|error}}(e) +
+

Errors the controlled readable stream, making all future interactions with it fail with the + given error e. +

-
- The ReadableByteStreamController constructor cannot be used directly; - {{ReadableByteStreamController}} instances are created automatically during {{ReadableStream}} construction. +
+ The byobRequest attribute's getter steps are: + + 1. If [=this=].\[[byobRequest]] is null and [=this=].\[[pendingPullIntos]] is not [=list/is + empty|empty=], + 1. Let |firstDescriptor| be [=this=].\[[pendingPullIntos]][0]. + 1. Let |view| be ! [$Construct$]({{%Uint8Array%}}, « |firstDescriptor|.\[[buffer]], + |firstDescriptor|.\[[byteOffset]] + |firstDescriptor|.\[[bytesFilled]], + |firstDescriptor|.\[[byteLength]] − |firstDescriptor|.\[[bytesFilled]] »). + 1. Let |byobRequest| be a [=new=] {{ReadableStreamBYOBRequest}}. + 1. Set |byobRequest|.\[[controller]] to [=this=]. + 1. Set |byobRequest|.\[[view]] to |view|. + 1. Set [=this=].\[[byobRequest]] to |byobRequest|. + 1. Return [=this=].\[[byobRequest]].
- - 1. Throw a *TypeError* exception. - - -

Properties of the {{ReadableByteStreamController}} prototype

- -
get byobRequest
+
+ The desiredSize attribute's getter steps are: -
- The byobRequest getter returns the current BYOB pull request. + 1. Return ! [$ReadableByteStreamControllerGetDesiredSize$]([=this=]).
- - 1. If ! IsReadableByteStreamController(*this*) is *false*, throw a *TypeError* exception. - 1. If *this*.[[byobRequest]] is *undefined* and *this*.[[pendingPullIntos]] is not empty, - 1. Let _firstDescriptor_ be the first element of *this*.[[pendingPullIntos]]. - 1. Let _view_ be ! Construct(%Uint8Array%, « _firstDescriptor_.[[buffer]], - _firstDescriptor_.[[byteOffset]] + _firstDescriptor_.[[bytesFilled]], _firstDescriptor_.[[byteLength]] − - _firstDescriptor_.[[bytesFilled]] »). - 1. Let _byobRequest_ be ObjectCreate(the original value of `ReadableStreamBYOBRequest`'s `prototype` - property). - 1. Perform ! SetUpReadableStreamBYOBRequest(_byobRequest_, *this*, _view_). - 1. Set *this*.[[byobRequest]] to _byobRequest_. - 1. Return *this*.[[byobRequest]]. - - -
get desiredSize
+
+ The close() method + steps are: -
- The desiredSize getter returns the desired size - to fill the controlled stream's internal queue. It can be negative, if the queue is over-full. An underlying - source ought to use this information to determine when and how to apply backpressure. + 1. If [=this=].\[[closeRequested]] is true, throw a {{TypeError}} exception. + 1. If [=this=].\[[controlledReadableStream]].\[[state]] is not "`readable`", throw a {{TypeError}} + exception. + 1. Perform ? [$ReadableByteStreamControllerClose$]([=this=]).
- - 1. If ! IsReadableByteStreamController(*this*) is *false*, throw a *TypeError* exception. - 1. Return ! ReadableByteStreamControllerGetDesiredSize(*this*). - - -
close()
- -
- The close method will close the controlled readable stream. Consumers will still be able to read - any previously-enqueued chunks from the stream, but once those are read, the stream will become closed. +
+ The enqueue(|chunk|) method steps are: + + 1. If |chunk|.\[[ByteLength]] is 0, throw a {{TypeError}} exception. + 1. If |chunk|.\[[ViewedArrayBuffer]].\[[ArrayBufferByteLength]] is 0, throw a {{TypeError}} + exception. + 1. If [=this=].\[[closeRequested]] is true, throw a {{TypeError}} exception. + 1. If [=this=].\[[controlledReadableStream]].\[[state]] is not "`readable`", throw a {{TypeError}} + exception. + 1. Return ! [$ReadableByteStreamControllerEnqueue$]([=this=], |chunk|).
- - 1. If ! IsReadableByteStreamController(*this*) is *false*, throw a *TypeError* exception. - 1. If *this*.[[closeRequested]] is *true*, throw a *TypeError* exception. - 1. If *this*.[[controlledReadableByteStream]].[[state]] is not `"readable"`, throw a *TypeError* exception. - 1. Perform ? ReadableByteStreamControllerClose(*this*). - +
+ The error(|e|) + method steps are: -
enqueue(chunk)
- -
- The enqueue method will enqueue a given chunk in the controlled readable stream. + 1. Perform ! [$ReadableByteStreamControllerError$]([=this=], |e|).
- - 1. If ! IsReadableByteStreamController(*this*) is *false*, throw a *TypeError* exception. - 1. If *this*.[[closeRequested]] is *true*, throw a *TypeError* exception. - 1. If *this*.[[controlledReadableByteStream]].[[state]] is not `"readable"`, throw a *TypeError* exception. - 1. If Type(_chunk_) is not Object, throw a *TypeError* exception. - 1. If _chunk_ does not have a [[ViewedArrayBuffer]] internal slot, throw a *TypeError* exception. - 1. If ! IsDetachedBuffer(_chunk_.[[ViewedArrayBuffer]]) is *true*, throw a *TypeError* exception. - 1. Return ! ReadableByteStreamControllerEnqueue(*this*, _chunk_). - +

Internal methods

-
error(e)
+The following are internal methods implemented by each {{ReadableByteStreamController}} instance. +The readable stream implementation will polymorphically call to either these, or to their +counterparts for default controllers, as discussed in [[#rs-abstract-ops-used-by-controllers]]. -
- The error method will error the readable stream, making all future interactions with it fail with the - given error e. -
- - - 1. If ! IsReadableByteStreamController(*this*) is *false*, throw a *TypeError* exception. - 1. Perform ! ReadableByteStreamControllerError(*this*, _e_). - - -

Readable stream BYOB controller internal methods

- -The following are additional internal methods implemented by each {{ReadableByteStreamController}} instance. The -readable stream implementation will polymorphically call to either these or their counterparts for default controllers. - -
\[[CancelSteps]](reason)
- - - 1. If *this*.[[pendingPullIntos]] is not empty, - 1. Let _firstDescriptor_ be the first element of *this*.[[pendingPullIntos]]. - 1. Set _firstDescriptor_.[[bytesFilled]] to *0*. - 1. Perform ! ResetQueue(*this*). - 1. Let _result_ be the result of performing *this*.[[cancelAlgorithm]], passing in _reason_. - 1. Perform ! ReadableByteStreamControllerClearAlgorithms(*this*). - 1. Return _result_. - - -
\[[PullSteps]]( )
- - - 1. Let _stream_ be *this*.[[controlledReadableByteStream]]. - 1. Assert: ! ReadableStreamHasDefaultReader(_stream_) is *true*. - 1. If *this*.[[queueTotalSize]] > *0*, - 1. Assert: ! ReadableStreamGetNumReadRequests(_stream_) is *0*. - 1. Let _entry_ be the first element of *this*.[[queue]]. - 1. Remove _entry_ from *this*.[[queue]], shifting all other elements downward (so that the second becomes the - first, and so on). - 1. Set *this*.[[queueTotalSize]] to *this*.[[queueTotalSize]] − _entry_.[[byteLength]]. - 1. Perform ! ReadableByteStreamControllerHandleQueueDrain(*this*). - 1. Let _view_ be ! Construct(%Uint8Array%, « _entry_.[[buffer]], _entry_.[[byteOffset]], - _entry_.[[byteLength]] »). - 1. Return a promise resolved with ! ReadableStreamCreateReadResult(_view_, *false*, - _stream_.[[reader]].[[forAuthorCode]]). - 1. Let _autoAllocateChunkSize_ be *this*.[[autoAllocateChunkSize]]. - 1. If _autoAllocateChunkSize_ is not *undefined*, - 1. Let _buffer_ be Construct(%ArrayBuffer%, « _autoAllocateChunkSize_ »). - 1. If _buffer_ is an abrupt completion, return a promise rejected with _buffer_.[[Value]]. - 1. Let _pullIntoDescriptor_ be Record {[[buffer]]: _buffer_.[[Value]], [[byteOffset]]: *0*, [[byteLength]]: - _autoAllocateChunkSize_, [[bytesFilled]]: *0*, [[elementSize]]: *1*, [[ctor]]: %Uint8Array%, - [[readerType]]: `"default"`}. - 1. Append _pullIntoDescriptor_ as the last element of *this*.[[pendingPullIntos]]. - 1. Let _promise_ be ! ReadableStreamAddReadRequest(_stream_). - 1. Perform ! ReadableByteStreamControllerCallPullIfNeeded(*this*). - 1. Return _promise_. - - -

Class -ReadableStreamBYOBRequest

- -The {{ReadableStreamBYOBRequest}} class represents a pull into request in a {{ReadableByteStreamController}}. - -

Class definition

+
+ \[[CancelSteps]](|reason|) implements the + [$ReadableStreamController/[[CancelSteps]]$] contract. It performs the following steps: -
+ 1. If [=this=].\[[pendingPullIntos]] is not [=list/is empty|empty=], + 1. Let |firstDescriptor| be [=this=].\[[pendingPullIntos]][0]. + 1. Set |firstDescriptor|.\[[bytesFilled]] to 0. + 1. Perform ! [$ResetQueue$]([=this=]). + 1. Let |result| be the result of performing [=this=].\[[cancelAlgorithm]], passing in |reason|. + 1. Perform ! [$ReadableByteStreamControllerClearAlgorithms$]([=this=]). + 1. Return |result|. +
-This section is non-normative. +
+ \[[PullSteps]]() implements the + [$ReadableStreamController/[[PullSteps]]$] contract. It performs the following steps: + + 1. Let |stream| be [=this=].\[[controlledReadableStream]]. + 1. Assert: ! [$ReadableStreamHasDefaultReader$](|stream|) is true. + 1. If [=this=].\[[queueTotalSize]] > 0, + 1. Assert: ! [$ReadableStreamGetNumReadRequests$](|stream|) is 0. + 1. Let |entry| be [=this=].\[[queue]][0]. + 1. [=list/Remove=] |entry| from [=this=].\[[queue]]. + 1. Set [=this=].\[[queueTotalSize]] to [=this=].\[[queueTotalSize]] − |entry|.\[[byteLength]]. + 1. Perform ! [$ReadableByteStreamControllerHandleQueueDrain$]([=this=]). + 1. Let |view| be ! [$Construct$]({{%Uint8Array%}}, « |entry|.\[[buffer]], |entry|.\[[byteOffset]], + |entry|.\[[byteLength]] »). + 1. Return [=a promise resolved with=] ! [$ReadableStreamCreateReadResult$](|view|, false, + |stream|.\[[reader]].\[[forAuthorCode]]). + 1. Let |autoAllocateChunkSize| be [=this=].\[[autoAllocateChunkSize]]. + 1. If |autoAllocateChunkSize| is not undefined, + 1. Let |buffer| be [$Construct$]({{%ArrayBuffer%}}, « |autoAllocateChunkSize| »). + 1. If |buffer| is an abrupt completion, return [=a promise rejected with=] |buffer|.\[[Value]]. + 1. Let |pullIntoDescriptor| be Record {\[[buffer]]: |buffer|.\[[Value]], \[[byteOffset]]: 0, + \[[byteLength]]: |autoAllocateChunkSize|, \[[bytesFilled]]: 0, \[[elementSize]]: 1, \[[ctor]]: + {{%Uint8Array%}}, \[[readerType]]: "`default`"}. + 1. [=list/Append=] |pullIntoDescriptor| to [=this=].\[[pendingPullIntos]]. + 1. Let |promise| be ! [$ReadableStreamAddReadRequest$](|stream|). + 1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$]([=this=]). + 1. Return |promise|. +
-If one were to write the {{ReadableStreamBYOBRequest}} class in something close to the syntax of [[!ECMASCRIPT]], it -would look like +

The {{ReadableStreamBYOBRequest}} class

-

-  class ReadableStreamBYOBRequest {
-    constructor(controller, view)
+The {{ReadableStreamBYOBRequest}} class represents a pull-into request in a
+{{ReadableByteStreamController}}.
 
-    get view()
+

Interface definition

- respond(bytesWritten) - respondWithNewView(view) - } -
+The Web IDL definition for the {{ReadableStreamBYOBRequest}} class is given as follows: -
+ +[Exposed=(Window,Worker,Worklet)] +interface ReadableStreamBYOBRequest { + readonly attribute ArrayBufferView? view; + + void respond([EnforceRange] unsigned long long bytesWritten); + void respondWithNewView(ArrayBufferView view); +}; +

Internal slots

-Instances of {{ReadableStreamBYOBRequest}} are created with the internal slots described in the following table: +Instances of {{ReadableStreamBYOBRequest}} are created with the internal slots described in the +following table: - - - - - - + - - - + + + + + +
Internal SlotDescription (non-normative)
\[[associatedReadableByteStreamController]] - The parent {{ReadableByteStreamController}} instance -
\[[view]] - A typed array representing the destination region to which the controller can write - generated data -
Internal SlotDescription (non-normative)
\[[controller]] + The parent {{ReadableByteStreamController}} instance +
\[[view]] + A [=typed array=] representing the destination region to which the + controller can write generated data, or null after the BYOB request has been invalidated.
-

new -ReadableStreamBYOBRequest()

+

Methods and properties

+ +
+
view = byobRequest.{{ReadableStreamBYOBRequest/view}} +
+

Returns the view for writing in to, or null if the BYOB request has already been responded to. - - 1. Throw a *TypeError* exception. - +

byobRequest.{{ReadableStreamBYOBRequest/respond()|respond}}(bytesWritten) +
+

Indicates to the associated [=readable byte stream=] that bytesWritten bytes + were written into {{ReadableStreamBYOBRequest/view}}, causing the result be surfaced to the + [=consumer=]. -

Properties of the {{ReadableStreamBYOBRequest}} prototype

+

After this method is called, {{ReadableStreamBYOBRequest/view}} will be transferred and no longer modifiable. -

get view
+
byobRequest.{{ReadableStreamBYOBRequest/respondWithNewView()|respondWithNewView}}(view) +
+

Indicates to the associated [=readable byte stream=] that instead of writing into + {{ReadableStreamBYOBRequest/view}}, the [=underlying byte source=] is providing a new + {{ArrayBufferView}}, which will be given to the [=consumer=] of the [=readable byte stream=]. - - 1. If ! IsReadableStreamBYOBRequest(*this*) is *false*, throw a *TypeError* exception. - 1. Return *this*.[[view]]. - +

After this method is called, view will be transferred and no longer modifiable. +

-
respond(bytesWritten)
+
+ The view + attribute's getter steps are: - - 1. If ! IsReadableStreamBYOBRequest(*this*) is *false*, throw a *TypeError* exception. - 1. If *this*.[[associatedReadableByteStreamController]] is *undefined*, throw a *TypeError* exception. - 1. If ! IsDetachedBuffer(*this*.[[view]].[[ViewedArrayBuffer]]) is *true*, throw a *TypeError* exception. - 1. Return ? ReadableByteStreamControllerRespond(*this*.[[associatedReadableByteStreamController]], _bytesWritten_). - + 1. Return [=this=].\[[view]]. +
-
respondWithNewView(view)
+
+ The respond(|bytesWritten|) method steps are: - - 1. If ! IsReadableStreamBYOBRequest(*this*) is *false*, throw a *TypeError* exception. - 1. If *this*.[[associatedReadableByteStreamController]] is *undefined*, throw a *TypeError* exception. - 1. If Type(_view_) is not Object, throw a *TypeError* exception. - 1. If _view_ does not have a [[ViewedArrayBuffer]] internal slot, throw a *TypeError* exception. - 1. If ! IsDetachedBuffer(_view_.[[ViewedArrayBuffer]]) is *true*, throw a *TypeError* exception. - 1. Return ? ReadableByteStreamControllerRespondWithNewView(*this*.[[associatedReadableByteStreamController]], _view_). - + 1. If [=this=].\[[controller]] is undefined, throw a {{TypeError}} exception. + 1. If [$IsDetachedBuffer$]([=this=].\[[view]].\[[ArrayBuffer]]) is true, throw a {{TypeError}} + exception. + 1. Assert: [=this=].\[[view]].\[[ByteLength]] > 0. + 1. Assert: [=this=].\[[view]].\[[ViewedArrayBuffer]].\[[ByeLength]] > 0. + 1. Perform ? [$ReadableByteStreamControllerRespond$]([=this=].\[[controller]], |bytesWritten|). +
-

Readable stream BYOB controller abstract operations

+
+ The respondWithNewView(|view|) method steps are: -

IsReadableStreamBYOBRequest ( -x )

+ 1. If |view|.\[[ByteLength]] is 0, throw a {{TypeError}} exception. + 1. If |view|.\[[ViewedArrayBuffer]].\[[ArrayBufferByteLength]] is 0, throw a {{TypeError}} + exception. + 1. If [=this=].\[[controller]] is undefined, throw a {{TypeError}} exception. + 1. Return ? [$ReadableByteStreamControllerRespondWithNewView$]([=this=].\[[controller]], |view|). +
- - 1. If Type(_x_) is not Object, return *false*. - 1. If _x_ does not have an [[associatedReadableByteStreamController]] internal slot, return *false*. - 1. Return *true*. - +

Abstract operations

+

Working with readable streams

-

IsReadableByteStreamController -( x )

+The following abstract operations operate on {{ReadableStream}} instances at a higher level. Some +are even meant to be generally useful by other specifications. - - 1. If Type(_x_) is not Object, return *false*. - 1. If _x_ does not have an [[controlledReadableByteStream]] internal slot, return *false*. - 1. Return *true*. - +
+ AcquireReadableStreamBYOBReader(|stream|[, |forAuthorCode|]) is meant to be called + from other specifications that wish to acquire a [=BYOB reader=] for a given stream. It performs + the following steps: -

ReadableByteStreamControllerCallPullIfNeeded ( controller )

+ 1. If |forAuthorCode| was not passed, set it to false. + 1. Let |reader| be a [=new=] {{ReadableStreamBYOBReader}}. + 1. Perform ? [$SetUpReadableStreamBYOBReader$](|reader|, |stream|). + 1. Set |reader|.\[[forAuthorCode]] to |forAuthorCode|. + 1. Return |reader|. - - 1. Let _shouldPull_ be ! ReadableByteStreamControllerShouldCallPull(_controller_). - 1. If _shouldPull_ is *false*, return. - 1. If _controller_.[[pulling]] is *true*, - 1. Set _controller_.[[pullAgain]] to *true*. - 1. Return. - 1. Assert: _controller_.[[pullAgain]] is *false*. - 1. Set _controller_.[[pulling]] to *true*. - 1. Let _pullPromise_ be the result of performing _controller_.[[pullAlgorithm]]. - 1. Upon fulfillment of _pullPromise_, - 1. Set _controller_.[[pulling]] to *false*. - 1. If _controller_.[[pullAgain]] is *true*, - 1. Set _controller_.[[pullAgain]] to *false*. - 1. Perform ! ReadableByteStreamControllerCallPullIfNeeded(_controller_). - 1. Upon rejection of _pullPromise_ with reason _e_, - 1. Perform ! ReadableByteStreamControllerError(_controller_, _e_). - - -

ReadableByteStreamControllerClearAlgorithms ( controller )

- -This abstract operation is called once the stream is closed or errored and the algorithms will not be executed any more. -By removing the algorithm references it permits the underlying source object to be garbage collected even if the -{{ReadableStream}} itself is still referenced. - -

The results of this algorithm are not currently observable, but could become so if JavaScript eventually -adds weak references. But even without that factor, -implementations will likely want to include similar steps.

- - - 1. Set _controller_.[[pullAlgorithm]] to *undefined*. - 1. Set _controller_.[[cancelAlgorithm]] to *undefined*. - - -

ReadableByteStreamControllerClearPendingPullIntos ( -controller )

- - - 1. Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(_controller_). - 1. Set _controller_.[[pendingPullIntos]] to a new empty List. - - -

ReadableByteStreamControllerClose ( controller )

- - - 1. Let _stream_ be _controller_.[[controlledReadableByteStream]]. - 1. If _controller_.[[closeRequested]] is *true* or _stream_.[[state]] is not `"readable"`, return. - 1. If _controller_.[[queueTotalSize]] > *0*, - 1. Set _controller_.[[closeRequested]] to *true*. - 1. Return. - 1. If _controller_.[[pendingPullIntos]] is not empty, - 1. Let _firstPendingPullInto_ be the first element of _controller_.[[pendingPullIntos]]. - 1. If _firstPendingPullInto_.[[bytesFilled]] > *0*, - 1. Let _e_ be a new *TypeError* exception. - 1. Perform ! ReadableByteStreamControllerError(_controller_, _e_). - 1. Throw _e_. - 1. Perform ! ReadableByteStreamControllerClearAlgorithms(_controller_). - 1. Perform ! ReadableStreamClose(_stream_). - - -

ReadableByteStreamControllerCommitPullIntoDescriptor -( stream, pullIntoDescriptor )

- - - 1. Assert: _stream_.[[state]] is not `"errored"`. - 1. Let _done_ be *false*. - 1. If _stream_.[[state]] is `"closed"`, - 1. Assert: _pullIntoDescriptor_.[[bytesFilled]] is *0*. - 1. Set _done_ to *true*. - 1. Let _filledView_ be ! ReadableByteStreamControllerConvertPullIntoDescriptor(_pullIntoDescriptor_). - 1. If _pullIntoDescriptor_.[[readerType]] is `"default"`, - 1. Perform ! ReadableStreamFulfillReadRequest(_stream_, _filledView_, _done_). - 1. Otherwise, - 1. Assert: _pullIntoDescriptor_.[[readerType]] is `"byob"`. - 1. Perform ! ReadableStreamFulfillReadIntoRequest(_stream_, _filledView_, _done_). - - -

ReadableByteStreamControllerConvertPullIntoDescriptor ( pullIntoDescriptor )

- - - 1. Let _bytesFilled_ be _pullIntoDescriptor_.[[bytesFilled]]. - 1. Let _elementSize_ be _pullIntoDescriptor_.[[elementSize]]. - 1. Assert: _bytesFilled_ ≤ _pullIntoDescriptor_.[[byteLength]]. - 1. Assert: _bytesFilled_ mod _elementSize_ is *0*. - 1. Return ! Construct(_pullIntoDescriptor_.[[ctor]], « _pullIntoDescriptor_.[[buffer]], - _pullIntoDescriptor_.[[byteOffset]], _bytesFilled_ ÷ _elementSize_ »). - - -

ReadableByteStreamControllerEnqueue ( controller, chunk )

- - - 1. Let _stream_ be _controller_.[[controlledReadableByteStream]]. - 1. If _controller_.[[closeRequested]] is *true* or _stream_.[[state]] is not `"readable"`, return. - 1. Let _buffer_ be _chunk_.[[ViewedArrayBuffer]]. - 1. Let _byteOffset_ be _chunk_.[[ByteOffset]]. - 1. Let _byteLength_ be _chunk_.[[ByteLength]]. - 1. Let _transferredBuffer_ be ! TransferArrayBuffer(_buffer_). - 1. If ! ReadableStreamHasDefaultReader(_stream_) is *true* - 1. If ! ReadableStreamGetNumReadRequests(_stream_) is *0*, - 1. Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(_controller_, _transferredBuffer_, _byteOffset_, - _byteLength_). - 1. Otherwise, - 1. Assert: _controller_.[[queue]] is empty. - 1. Let _transferredView_ be ! Construct(%Uint8Array%, « _transferredBuffer_, _byteOffset_, - _byteLength_ »). - 1. Perform ! ReadableStreamFulfillReadRequest(_stream_, _transferredView_, *false*). - 1. Otherwise, if ! ReadableStreamHasBYOBReader(_stream_) is *true*, - 1. Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(_controller_, _transferredBuffer_, _byteOffset_, - _byteLength_). - 1. Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(_controller_). - 1. Otherwise, - 1. Assert: ! IsReadableStreamLocked(_stream_) is *false*. - 1. Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(_controller_, _transferredBuffer_, _byteOffset_, - _byteLength_). - 1. Perform ! ReadableByteStreamControllerCallPullIfNeeded(_controller_). - - -

ReadableByteStreamControllerEnqueueChunkToQueue ( controller, buffer, -byteOffset, byteLength )

- - - 1. Append Record {[[buffer]]: _buffer_, [[byteOffset]]: _byteOffset_, [[byteLength]]: _byteLength_} as the last - element of _controller_.[[queue]]. - 1. Add _byteLength_ to _controller_.[[queueTotalSize]]. - - -

ReadableByteStreamControllerError ( controller, e )

- - - 1. Let _stream_ be _controller_.[[controlledReadableByteStream]]. - 1. If _stream_.[[state]] is not `"readable"`, return. - 1. Perform ! ReadableByteStreamControllerClearPendingPullIntos(_controller_). - 1. Perform ! ResetQueue(_controller_). - 1. Perform ! ReadableByteStreamControllerClearAlgorithms(_controller_). - 1. Perform ! ReadableStreamError(_stream_, _e_). - - -

ReadableByteStreamControllerFillHeadPullIntoDescriptor ( controller, size, -pullIntoDescriptor )

- - - 1. Assert: either _controller_.[[pendingPullIntos]] is empty, or the first element of - _controller_.[[pendingPullIntos]] is _pullIntoDescriptor_. - 1. Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(_controller_). - 1. Set _pullIntoDescriptor_.[[bytesFilled]] to _pullIntoDescriptor_.[[bytesFilled]] + _size_. - - -

ReadableByteStreamControllerFillPullIntoDescriptorFromQueue ( controller, -pullIntoDescriptor )

- - - 1. Let _elementSize_ be _pullIntoDescriptor_.[[elementSize]]. - 1. Let _currentAlignedBytes_ be _pullIntoDescriptor_.[[bytesFilled]] − (_pullIntoDescriptor_.[[bytesFilled]] mod - _elementSize_). - 1. Let _maxBytesToCopy_ be min(_controller_.[[queueTotalSize]], _pullIntoDescriptor_.[[byteLength]] − - _pullIntoDescriptor_.[[bytesFilled]]). - 1. Let _maxBytesFilled_ be _pullIntoDescriptor_.[[bytesFilled]] + _maxBytesToCopy_. - 1. Let _maxAlignedBytes_ be _maxBytesFilled_ − (_maxBytesFilled_ mod _elementSize_). - 1. Let _totalBytesToCopyRemaining_ be _maxBytesToCopy_. - 1. Let _ready_ be *false*. - 1. If _maxAlignedBytes_ > _currentAlignedBytes_, - 1. Set _totalBytesToCopyRemaining_ to _maxAlignedBytes_ − _pullIntoDescriptor_.[[bytesFilled]]. - 1. Set _ready_ to *true*. - 1. Let _queue_ be _controller_.[[queue]]. - 1. Repeat the following steps while _totalBytesToCopyRemaining_ > *0*, - 1. Let _headOfQueue_ be the first element of _queue_. - 1. Let _bytesToCopy_ be min(_totalBytesToCopyRemaining_, _headOfQueue_.[[byteLength]]). - 1. Let _destStart_ be _pullIntoDescriptor_.[[byteOffset]] + _pullIntoDescriptor_.[[bytesFilled]]. - 1. Perform ! CopyDataBlockBytes(_pullIntoDescriptor_.[[buffer]].[[ArrayBufferData]], _destStart_, - _headOfQueue_.[[buffer]].[[ArrayBufferData]], _headOfQueue_.[[byteOffset]], _bytesToCopy_). - 1. If _headOfQueue_.[[byteLength]] is _bytesToCopy_, - 1. Remove the first element of _queue_, shifting all other elements downward (so that the second becomes the - first, and so on). - 1. Otherwise, - 1. Set _headOfQueue_.[[byteOffset]] to _headOfQueue_.[[byteOffset]] + _bytesToCopy_. - 1. Set _headOfQueue_.[[byteLength]] to _headOfQueue_.[[byteLength]] − _bytesToCopy_. - 1. Set _controller_.[[queueTotalSize]] to _controller_.[[queueTotalSize]] − _bytesToCopy_. - 1. Perform ! ReadableByteStreamControllerFillHeadPullIntoDescriptor(_controller_, _bytesToCopy_, - _pullIntoDescriptor_). - 1. Set _totalBytesToCopyRemaining_ to _totalBytesToCopyRemaining_ − _bytesToCopy_. - 1. If _ready_ is *false*, - 1. Assert: _controller_.[[queueTotalSize]] is *0*. - 1. Assert: _pullIntoDescriptor_.[[bytesFilled]] > *0*. - 1. Assert: _pullIntoDescriptor_.[[bytesFilled]] < _pullIntoDescriptor_.[[elementSize]]. - 1. Return _ready_. - - -

ReadableByteStreamControllerGetDesiredSize ( controller )

- - - 1. Let _stream_ be _controller_.[[controlledReadableByteStream]]. - 1. Let _state_ be _stream_.[[state]]. - 1. If _state_ is `"errored"`, return *null*. - 1. If _state_ is `"closed"`, return *0*. - 1. Return _controller_.[[strategyHWM]] − _controller_.[[queueTotalSize]]. - - -

ReadableByteStreamControllerHandleQueueDrain ( controller )

- - - 1. Assert: _controller_.[[controlledReadableByteStream]].[[state]] is `"readable"`. - 1. If _controller_.[[queueTotalSize]] is *0* and _controller_.[[closeRequested]] is *true*, - 1. Perform ! ReadableByteStreamControllerClearAlgorithms(_controller_). - 1. Perform ! ReadableStreamClose(_controller_.[[controlledReadableByteStream]]). - 1. Otherwise, - 1. Perform ! ReadableByteStreamControllerCallPullIfNeeded(_controller_). - - -

ReadableByteStreamControllerInvalidateBYOBRequest ( -controller )

- - - 1. If _controller_.[[byobRequest]] is *undefined*, return. - 1. Set _controller_.[[byobRequest]].[[associatedReadableByteStreamController]] to *undefined*. - 1. Set _controller_.[[byobRequest]].[[view]] to *undefined*. - 1. Set _controller_.[[byobRequest]] to *undefined*. - - -

ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue ( controller )

- - - 1. Assert: _controller_.[[closeRequested]] is *false*. - 1. Repeat the following steps while _controller_.[[pendingPullIntos]] is not empty, - 1. If _controller_.[[queueTotalSize]] is *0*, return. - 1. Let _pullIntoDescriptor_ be the first element of _controller_.[[pendingPullIntos]]. - 1. If ! ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(_controller_, _pullIntoDescriptor_) is *true*, - 1. Perform ! ReadableByteStreamControllerShiftPendingPullInto(_controller_). - 1. Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(_controller_.[[controlledReadableByteStream]], - _pullIntoDescriptor_). - - -

ReadableByteStreamControllerPullInto ( controller, view )

- - - 1. Let _stream_ be _controller_.[[controlledReadableByteStream]]. - 1. Let _elementSize_ be 1. - 1. Let _ctor_ be %DataView%. - 1. If _view_ has a [[TypedArrayName]] internal slot (i.e., it is not a `DataView`), - 1. Set _elementSize_ to the element size specified in the typed array constructors table for - _view_.[[TypedArrayName]]. - 1. Set _ctor_ to the constructor specified in the typed array constructors table for - _view_.[[TypedArrayName]]. - 1. Let _byteOffset_ be _view_.[[ByteOffset]]. - 1. Let _byteLength_ be _view_.[[ByteLength]]. - 1. Let _buffer_ be ! TransferArrayBuffer(_view_.[[ViewedArrayBuffer]]). - 1. Let _pullIntoDescriptor_ be Record {[[buffer]]: _buffer_, [[byteOffset]]: _byteOffset_, - [[byteLength]]: _byteLength_, [[bytesFilled]]: *0*, [[elementSize]]: _elementSize_, - [[ctor]]: _ctor_, [[readerType]]: `"byob"`}. - 1. If _controller_.[[pendingPullIntos]] is not empty, - 1. Append _pullIntoDescriptor_ as the last element of _controller_.[[pendingPullIntos]]. - 1. Return ! ReadableStreamAddReadIntoRequest(_stream_). - 1. If _stream_.[[state]] is `"closed"`, - 1. Let _emptyView_ be ! Construct(_ctor_, « _pullIntoDescriptor_.[[buffer]], _pullIntoDescriptor_.[[byteOffset]], *0* »). - 1. Return a promise resolved with ! ReadableStreamCreateReadResult(_emptyView_, *true*, - _stream_.[[reader]].[[forAuthorCode]]). - 1. If _controller_.[[queueTotalSize]] > *0*, - 1. If ! ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(_controller_, _pullIntoDescriptor_) is *true*, - 1. Let _filledView_ be ! ReadableByteStreamControllerConvertPullIntoDescriptor(_pullIntoDescriptor_). - 1. Perform ! ReadableByteStreamControllerHandleQueueDrain(_controller_). - 1. Return a promise resolved with ! ReadableStreamCreateReadResult(_filledView_, *false*, - _stream_.[[reader]].[[forAuthorCode]]). - 1. If _controller_.[[closeRequested]] is *true*, - 1. Let _e_ be a *TypeError* exception. - 1. Perform ! ReadableByteStreamControllerError(_controller_, _e_). - 1. Return a promise rejected with _e_. - 1. Append _pullIntoDescriptor_ as the last element of _controller_.[[pendingPullIntos]]. - 1. Let _promise_ be ! ReadableStreamAddReadIntoRequest(_stream_). - 1. Perform ! ReadableByteStreamControllerCallPullIfNeeded(_controller_). - 1. Return _promise_. - - -

ReadableByteStreamControllerRespond ( controller, bytesWritten )

- - - 1. Let _bytesWritten_ be ? ToNumber(_bytesWritten_). - 1. If ! IsFiniteNonNegativeNumber(_bytesWritten_) is *false*, - 1. Throw a *RangeError* exception. - 1. Assert: _controller_.[[pendingPullIntos]] is not empty. - 1. Perform ? ReadableByteStreamControllerRespondInternal(_controller_, _bytesWritten_). - - -

ReadableByteStreamControllerRespondInClosedState ( controller, -firstDescriptor )

- - - 1. Set _firstDescriptor_.[[buffer]] to ! TransferArrayBuffer(_firstDescriptor_.[[buffer]]). - 1. Assert: _firstDescriptor_.[[bytesFilled]] is *0*. - 1. Let _stream_ be _controller_.[[controlledReadableByteStream]]. - 1. If ! ReadableStreamHasBYOBReader(_stream_) is *true*, - 1. Repeat the following steps while ! ReadableStreamGetNumReadIntoRequests(_stream_) > *0*, - 1. Let _pullIntoDescriptor_ be ! ReadableByteStreamControllerShiftPendingPullInto(_controller_). - 1. Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(_stream_, _pullIntoDescriptor_). - - -

ReadableByteStreamControllerRespondInReadableState ( -controller, bytesWritten, pullIntoDescriptor )

- - - 1. If _pullIntoDescriptor_.[[bytesFilled]] + _bytesWritten_ > _pullIntoDescriptor_.[[byteLength]], throw a - *RangeError* exception. - 1. Perform ! ReadableByteStreamControllerFillHeadPullIntoDescriptor(_controller_, _bytesWritten_, - _pullIntoDescriptor_). - 1. If _pullIntoDescriptor_.[[bytesFilled]] < _pullIntoDescriptor_.[[elementSize]], return. - 1. Perform ! ReadableByteStreamControllerShiftPendingPullInto(_controller_). - 1. Let _remainderSize_ be _pullIntoDescriptor_.[[bytesFilled]] mod _pullIntoDescriptor_.[[elementSize]]. - 1. If _remainderSize_ > *0*, - 1. Let _end_ be _pullIntoDescriptor_.[[byteOffset]] + _pullIntoDescriptor_.[[bytesFilled]]. - 1. Let _remainder_ be ? CloneArrayBuffer(_pullIntoDescriptor_.[[buffer]], _end_ − _remainderSize_, _remainderSize_, - %ArrayBuffer%). - 1. Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(_controller_, _remainder_, *0*, - _remainder_.[[ByteLength]]). - 1. Set _pullIntoDescriptor_.[[buffer]] to ! TransferArrayBuffer(_pullIntoDescriptor_.[[buffer]]). - 1. Set _pullIntoDescriptor_.[[bytesFilled]] to _pullIntoDescriptor_.[[bytesFilled]] − _remainderSize_. - 1. Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(_controller_.[[controlledReadableByteStream]], - _pullIntoDescriptor_). - 1. Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(_controller_). - - -

ReadableByteStreamControllerRespondInternal ( controller, bytesWritten )

- - - 1. Let _firstDescriptor_ be the first element of _controller_.[[pendingPullIntos]]. - 1. Let _stream_ be _controller_.[[controlledReadableByteStream]]. - 1. If _stream_.[[state]] is `"closed"`, - 1. If _bytesWritten_ is not *0*, throw a *TypeError* exception. - 1. Perform ! ReadableByteStreamControllerRespondInClosedState(_controller_, _firstDescriptor_). - 1. Otherwise, - 1. Assert: _stream_.[[state]] is `"readable"`. - 1. Perform ? ReadableByteStreamControllerRespondInReadableState(_controller_, _bytesWritten_, _firstDescriptor_). - 1. Perform ! ReadableByteStreamControllerCallPullIfNeeded(_controller_). - - -

ReadableByteStreamControllerRespondWithNewView ( controller, view )

- - - 1. Assert: _controller_.[[pendingPullIntos]] is not empty. - 1. Let _firstDescriptor_ be the first element of _controller_.[[pendingPullIntos]]. - 1. If _firstDescriptor_.[[byteOffset]] + _firstDescriptor_.[[bytesFilled]] is not _view_.[[ByteOffset]], throw a - *RangeError* exception. - 1. If _firstDescriptor_.[[byteLength]] is not _view_.[[ByteLength]], throw a *RangeError* exception. - 1. Set _firstDescriptor_.[[buffer]] to _view_.[[ViewedArrayBuffer]]. - 1. Perform ? ReadableByteStreamControllerRespondInternal(_controller_, _view_.[[ByteLength]]). - - -

ReadableByteStreamControllerShiftPendingPullInto ( controller )

- - - 1. Let _descriptor_ be the first element of _controller_.[[pendingPullIntos]]. - 1. Remove _descriptor_ from _controller_.[[pendingPullIntos]], shifting all other elements downward (so that the - second becomes the first, and so on). - 1. Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(_controller_). - 1. Return _descriptor_. - - -

ReadableByteStreamControllerShouldCallPull ( controller )

- - - 1. Let _stream_ be _controller_.[[controlledReadableByteStream]]. - 1. If _stream_.[[state]] is not `"readable"`, return *false*. - 1. If _controller_.[[closeRequested]] is *true*, return *false*. - 1. If _controller_.[[started]] is *false*, return *false*. - 1. If ! ReadableStreamHasDefaultReader(_stream_) is *true* and ! ReadableStreamGetNumReadRequests(_stream_) > *0*, - return *true*. - 1. If ! ReadableStreamHasBYOBReader(_stream_) is *true* and ! ReadableStreamGetNumReadIntoRequests(_stream_) > *0*, - return *true*. - 1. Let _desiredSize_ be ! ReadableByteStreamControllerGetDesiredSize(_controller_). - 1. Assert: _desiredSize_ is not *null*. - 1. If _desiredSize_ > *0*, return *true*. - 1. Return *false*. - - -

SetUpReadableByteStreamController ( stream, controller, startAlgorithm, -pullAlgorithm, cancelAlgorithm, highWaterMark, autoAllocateChunkSize )

- - - 1. Assert: _stream_.[[readableStreamController]] is *undefined*. - 1. If _autoAllocateChunkSize_ is not *undefined*, - 1. Assert: ! IsInteger(_autoAllocateChunkSize_) is *true*. - 1. Assert: _autoAllocateChunkSize_ is positive. - 1. Set _controller_.[[controlledReadableByteStream]] to _stream_. - 1. Set _controller_.[[pullAgain]] and _controller_.[[pulling]] to *false*. - 1. Set _controller_.[[byobRequest]] to *undefined*. - 1. Perform ! ResetQueue(_controller_). - 1. Set _controller_.[[closeRequested]] and _controller_.[[started]] to *false*. - 1. Set _controller_.[[strategyHWM]] to ? ValidateAndNormalizeHighWaterMark(_highWaterMark_). - 1. Set _controller_.[[pullAlgorithm]] to _pullAlgorithm_. - 1. Set _controller_.[[cancelAlgorithm]] to _cancelAlgorithm_. - 1. Set _controller_.[[autoAllocateChunkSize]] to _autoAllocateChunkSize_. - 1. Set _controller_.[[pendingPullIntos]] to a new empty List. - 1. Set _stream_.[[readableStreamController]] to _controller_. - 1. Let _startResult_ be the result of performing _startAlgorithm_. - 1. Let _startPromise_ be a promise resolved with _startResult_. - 1. Upon fulfillment of _startPromise_, - 1. Set _controller_.[[started]] to *true*. - 1. Assert: _controller_.[[pulling]] is *false*. - 1. Assert: _controller_.[[pullAgain]] is *false*. - 1. Perform ! ReadableByteStreamControllerCallPullIfNeeded(_controller_). - 1. Upon rejection of _startPromise_ with reason _r_, - 1. Perform ! ReadableByteStreamControllerError(_controller_, _r_). - - -

SetUpReadableByteStreamControllerFromUnderlyingSource ( stream, -underlyingByteSource, highWaterMark )

- - - 1. Assert: _underlyingByteSource_ is not *undefined*. - 1. Let _controller_ be ObjectCreate(the original value of `ReadableByteStreamController`'s `prototype` - property). - 1. Let _startAlgorithm_ be the following steps: - 1. Return ? InvokeOrNoop(_underlyingByteSource_, `"start"`, « _controller_ »). - 1. Let _pullAlgorithm_ be ? CreateAlgorithmFromUnderlyingMethod(_underlyingByteSource_, `"pull"`, *0*, « _controller_ - »). - 1. Let _cancelAlgorithm_ be ? CreateAlgorithmFromUnderlyingMethod(_underlyingByteSource_, `"cancel"`, *1*, « »). - 1. Let _autoAllocateChunkSize_ be ? GetV(_underlyingByteSource_, `"autoAllocateChunkSize"`). - 1. If _autoAllocateChunkSize_ is not *undefined*, - 1. Set _autoAllocateChunkSize_ to ? ToNumber(_autoAllocateChunkSize_). - 1. If ! IsInteger(_autoAllocateChunkSize_) is *false*, or if _autoAllocateChunkSize_ ≤ *0*, throw a *RangeError* - exception. - 1. Perform ? SetUpReadableByteStreamController(_stream_, _controller_, _startAlgorithm_, _pullAlgorithm_, - _cancelAlgorithm_, _highWaterMark_, _autoAllocateChunkSize_). - - -

SetUpReadableStreamBYOBRequest ( request, controller, view )

- - - 1. Assert: ! IsReadableByteStreamController(_controller_) is *true*. - 1. Assert: Type(_view_) is Object. - 1. Assert: _view_ has a [[ViewedArrayBuffer]] internal slot. - 1. Assert: ! IsDetachedBuffer(_view_.[[ViewedArrayBuffer]]) is *false*. - 1. Set _request_.[[associatedReadableByteStreamController]] to _controller_. - 1. Set _request_.[[view]] to _view_. - +

Other specifications ought to leave |forAuthorCode| as its default value of + false, unless they are planning to directly expose the resulting { value, done } + object to authors. See the note regarding + ReadableStreamCreateReadResult for more information. +

-

Writable streams

+
+ AcquireReadableStreamDefaultReader(|stream|[, |forAuthorCode|]) is meant to be called + from other specifications that wish to acquire a [=default reader=] for a given stream. It performs + the following steps: -

Using writable streams

+ 1. If |forAuthorCode| was not passed, set it to false. + 1. Let |reader| be a [=new=] {{ReadableStreamDefaultReader}}. + 1. Perform [$SetUpReadableStreamDefaultReader$](|reader|, |stream|). + 1. Set |reader|.\[[forAuthorCode]] to |forAuthorCode|. + 1. Return |reader|. -
- The usual way to write to a writable stream is to simply pipe a readable stream to it. - This ensures that backpressure is respected, so that if the writable stream's underlying sink is not - able to accept data as fast as the readable stream can produce it, the readable stream is informed of this and has a - chance to slow down its data production. +

The same usage note for the |forAuthorCode| parameter applies here as it does for + [$AcquireReadableStreamBYOBReader$]. +

-

-    readableStream.pipeTo(writableStream)
-      .then(() => console.log("All data successfully written!"))
-      .catch(e => console.error("Something went wrong!", e));
-  
+
+ CreateReadableStream(|startAlgorithm|, |pullAlgorithm|, |cancelAlgorithm|[, + |highWaterMark|, [, |sizeAlgorithm|]]) is meant to be called from other specifications that + wish to create {{ReadableStream}} instances. The |pullAlgorithm| and |cancelAlgorithm| algorithms + must return promises; if supplied, |sizeAlgorithm| must be an algorithm accepting [=chunk=] + objects and returning a number; and if supplied, |highWaterMark| must be a non-negative, non-NaN + number. + + It performs the following steps: + + 1. If |highWaterMark| was not passed, set it to 1. + 1. If |sizeAlgorithm| was not passed, set it to an algorithm that returns 1. + 1. Assert: ! [$IsNonNegativeNumber$](|highWaterMark|) is true. + 1. Let |stream| be a [=new=] {{ReadableStream}}. + 1. Perform ! [$InitializeReadableStream$](|stream|). + 1. Let |controller| be a [=new=] {{ReadableStreamDefaultController}}. + 1. Perform ? [$SetUpReadableStreamDefaultController$](|stream|, |controller|, |startAlgorithm|, + |pullAlgorithm|, |cancelAlgorithm|, |highWaterMark|, |sizeAlgorithm|). + 1. Return |stream|. + +

This abstract operation will throw an exception if and only if the supplied + |startAlgorithm| throws.

-
- You can also write directly to writable streams by acquiring a writer and using its - {{WritableStreamDefaultWriter/write()}} and {{WritableStreamDefaultWriter/close()}} methods. Since writable streams - queue any incoming writes, and take care internally to forward them to the underlying sink in sequence, you can - indiscriminately write to a writable stream without much ceremony: +
+ CreateReadableByteStream(|startAlgorithm|, |pullAlgorithm|, |cancelAlgorithm|[, + |highWaterMark|, [, |autoAllocateChunkSize|]]) is meant to be called from other + specifications that wish to create {{ReadableStream}} instances that represent [=readable byte + streams=]. The |pullAlgorithm| and |cancelAlgorithm| algorithms must return promises; if supplied, + |highWaterMark| must be a non-negative, non-NaN number, and, if supplied, |autoAllocateChunkSize| + must be a positive integer. + + It performs the following steps: + + 1. If |highWaterMark| was not passed, set it to 0. + 1. If |autoAllocateChunkSize| was not passed, set it to undefined. + 1. Assert: ! [$IsNonNegativeNumber$](|highWaterMark|) is true. + 1. If |autoAllocateChunkSize| is not undefined, + 1. Assert: ! [$IsInteger$](|autoAllocateChunkSize|) is true. + 1. Assert: |autoAllocateChunkSize| is positive. + 1. Let |stream| be a [=new=] {{ReadableStream}}. + 1. Perform ! [$InitializeReadableStream$](|stream|). + 1. Let |controller| be a [=new=] {{ReadableByteStreamController}}. + 1. Perform ? [$SetUpReadableByteStreamController$](|stream|, |controller|, |startAlgorithm|, + |pullAlgorithm|, |cancelAlgorithm|, |highWaterMark|, |autoAllocateChunkSize|). + 1. Return |stream|. + +

This abstract operation will throw an exception if and only if the supplied + |startAlgorithm| throws. +

-

-    function writeArrayToStream(array, writableStream) {
-      const writer = writableStream.getWriter();
-      array.forEach(chunk => writer.write(chunk).catch(() => {}));
+
+ InitializeReadableStream(|stream|) performs the following + steps: - return writer.close(); - } + 1. Set |stream|.\[[state]] to "`readable`". + 1. Set |stream|.\[[reader]] and |stream|.\[[storedError]] to undefined. + 1. Set |stream|.\[[disturbed]] to false. +
- writeArrayToStream([1, 2, 3, 4, 5], writableStream) - .then(() => console.log("All done!")) - .catch(e => console.error("Error with the stream: " + e)); -
+
+ IsReadableStreamDisturbed(|stream|) is meant to be called from other specifications + that wish to query whether or not a readable stream has ever been read from or canceled. It + performs the following steps: - Note how we use .catch(() => {}) to suppress any rejections from the - {{WritableStreamDefaultWriter/write()}} method; we'll be notified of any fatal errors via a rejection of the - {{WritableStreamDefaultWriter/close()}} method, and leaving them un-caught would cause potential - {{unhandledrejection}} events and console warnings. + 1. Return |stream|.\[[disturbed]].
-
- In the previous example we only paid attention to the success or failure of the entire stream, by looking at the - promise returned by the writer's {{WritableStreamDefaultWriter/close()}} method. That promise will reject if anything - goes wrong with the stream—initializing it, writing to it, or closing it. And it will fulfill once the stream is - successfully closed. Often this is all you care about. +
+ IsReadableStreamLocked(|stream|) is meant to be called from other specifications + that wish to query whether or not a readable stream is [=locked to a reader=]. - However, if you care about the success of writing a specific chunk, you can use the promise returned by the - writer's {{WritableStreamDefaultWriter/write()}} method: + 1. If |stream|.\[[reader]] is undefined, return false. + 1. Return true. +
-

-    writer.write("i am a chunk of data")
-      .then(() => console.log("chunk successfully written!"))
-      .catch(e => console.error(e));
-  
+
+ ReadableStreamPipeTo(|source|, |dest|, |preventClose|, |preventAbort|, |preventCancel|[, + |signal|]) is meant to be called from other specifications that wish to [=piping|pipe=] a + given readable stream to a destination [=writable stream=]. It performs the following steps: + + 1. Assert: |source| [=implements=] {{ReadableStream}}. + 1. Assert: |dest| [=implements=] {{WritableStream}}. + 1. Assert: |preventClose|, |preventAbort|, and |preventCancel| are all booleans. + 1. If |signal| is not given, let |signal| be undefined. + 1. Assert: either |signal| is undefined, or |signal| [=implements=] {{AbortSignal}}. + 1. Assert: ! [$IsReadableStreamLocked$](|source|) is false. + 1. Assert: ! [$IsWritableStreamLocked$](|dest|) is false. + 1. If |source|.\[[readableStreamController]] [=implements=] {{ReadableByteStreamController}}, let + |reader| be either ! [$AcquireReadableStreamBYOBReader$](|source|) or ! + [$AcquireReadableStreamDefaultReader$](|source|), at the user agent's discretion. + 1. Otherwise, let |reader| be ! [$AcquireReadableStreamDefaultReader$](|source|). + 1. Let |writer| be ! [$AcquireWritableStreamDefaultWriter$](|dest|). + 1. Set |source|.\[[disturbed]] to true. + 1. Let |shuttingDown| be false. + 1. Let |promise| be [=a new promise=]. + 1. If |signal| is not undefined, + 1. Let |abortAlgorithm| be the following steps: + 1. Let |error| be a new "{{AbortError}}" {{DOMException}}. + 1. Let |actions| be an empty [=ordered set=]. + 1. If |preventAbort| is false, [=set/append=] the following action to |actions|: + 1. If |dest|.\[[state]] is "`writable`", return ! [$WritableStreamAbort$](|dest|, |error|). + 1. Otherwise, return [=a promise resolved with=] undefined. + 1. If |preventCancel| is false, [=set/append=] the following action action to |actions|: + 1. If |source|.\[[state]] is "`readable`", return ! [$ReadableStreamCancel$](|source|, |error|). + 1. Otherwise, return [=a promise resolved with=] undefined. + 1. [=Shutdown with an action=] consisting of [=getting a promise to wait for all=] of the actions + in |actions|, and with |error|. + 1. If |signal|'s [=AbortSignal/aborted flag=] is set, perform |abortAlgorithm| and return + |promise|. + 1. [=AbortSignal/Add=] |abortAlgorithm| to |signal|. + 1. [=In parallel=] but not really; see #905, using |reader| and + |writer|, read all [=chunks=] from |source| and write them to |dest|. Due to the locking + provided by the reader and writer, the exact manner in which this happens is not observable to + author code, and so there is flexibility in how this is done. The following constraints apply + regardless of the exact algorithm used: + * Public API must not be used: while reading or writing, or performing any of + the operations below, the JavaScript-modifiable reader, writer, and stream APIs (i.e. methods + on the appropriate prototypes) must not be used. Instead, the streams must be manipulated + directly. + * Backpressure must be enforced: + * While [$WritableStreamDefaultWriterGetDesiredSize$](|writer|) is ≤ 0 or is null, the user + agent must not read from |reader|. + * If |reader| is a [=BYOB reader=], [$WritableStreamDefaultWriterGetDesiredSize$](|writer|) + should be used as a basis to determine the size of the chunks read from |reader|. +

It's frequently inefficient to read chunks that are too small or too large. + Other information might be factored in to determine the optimal chunk size. + * Reads or writes should not be delayed for reasons other than these backpressure signals. +

An implementation that waits for each write + to successfully complete before proceeding to the next read/write operation violates this + recommendation. In doing so, such an implementation makes the [=internal queue=] of |dest| + useless, as it ensures |dest| always contains at most one queued [=chunk=]. + * Shutdown must stop activity: if |shuttingDown| becomes true, the user agent + must not initiate further reads from |reader|, and must only perform writes of already-read + [=chunks=], as described below. In particular, the user agent must check the below conditions + before performing any reads or writes, since they might lead to immediate shutdown. + * Error and close states must be propagated: the following conditions must be + applied in order. + 1. Errors must be propagated forward: if |source|.\[[state]] is or becomes + "`errored`", then + 1. If |preventAbort| is false, [=shutdown with an action=] of ! [$WritableStreamAbort$](|dest|, + |source|.\[[storedError]]) and with |source|.\[[storedError]]. + 1. Otherwise, [=shutdown=] with |source|.\[[storedError]]. + 1. Errors must be propagated backward: if |dest|.\[[state]] is or becomes + "`errored`", then + 1. If |preventCancel| is false, [=shutdown with an action=] of ! + [$ReadableStreamCancel$](|source|, |dest|.\[[storedError]]) and with |dest|.\[[storedError]]. + 1. Otherwise, [=shutdown=] with |dest|.\[[storedError]]. + 1. Closing must be propagated forward: if |source|.\[[state]] is or becomes + "`closed`", then + 1. If |preventClose| is false, [=shutdown with an action=] of ! + [$WritableStreamDefaultWriterCloseWithErrorPropagation$](|writer|). + 1. Otherwise, [=shutdown=]. + 1. Closing must be propagated backward: if ! + [$WritableStreamCloseQueuedOrInFlight$](|dest|) is true or |dest|.\[[state]] is "`closed`", + then + 1. Assert: no [=chunks=] have been read or written. + 1. Let |destClosed| be a new {{TypeError}}. + 1. If |preventCancel| is false, [=shutdown with an action=] of ! + [$ReadableStreamCancel$](|source|, |destClosed|) and with |destClosed|. + 1. Otherwise, [=shutdown=] with |destClosed|. + * Shutdown with an action: if any of the + above requirements ask to shutdown with an action |action|, optionally with an error + |originalError|, then: + 1. If |shuttingDown| is true, abort these substeps. + 1. Set |shuttingDown| to true. + 1. If |dest|.\[[state]] is "`writable`" and ! [$WritableStreamCloseQueuedOrInFlight$](|dest|) is + false, + 1. If any [=chunks=] have been read but not yet written, write them to |dest|. + 1. Wait until every [=chunk=] that has been read has been written (i.e. the corresponding + promises have settled). + 1. Let |p| be the result of performing |action|. + 1. [=Upon fulfillment=] of |p|, [=finalize=], passing along |originalError| if it was given. + 1. [=Upon rejection=] of |p| with reason |newError|, [=finalize=] with |newError|. + * Shutdown: if any of the above requirements or steps + ask to shutdown, optionally with an error |error|, then: + 1. If |shuttingDown| is true, abort these substeps. + 1. Set |shuttingDown| to true. + 1. If |dest|.\[[state]] is "`writable`" and ! [$WritableStreamCloseQueuedOrInFlight$](|dest|) is + false, + 1. If any [=chunks=] have been read but not yet written, write them to |dest|. + 1. Wait until every [=chunk=] that has been read has been written (i.e. the corresponding + promises have settled). + 1. [=Finalize=], passing along |error| if it was given. + * Finalize: both forms of shutdown will eventually ask + to finalize, optionally with an error |error|, which means to perform the following steps: + 1. Perform ! [$WritableStreamDefaultWriterRelease$](|writer|). + 1. Perform ! [$ReadableStreamReaderGenericRelease$](|reader|). + 1. If |signal| is not undefined, [=AbortSignal/remove=] |abortAlgorithm| from |signal|. + 1. If |error| was given, [=reject=] |promise| with |error|. + 1. Otherwise, [=resolve=] |promise| with undefined. + 1. Return |promise|. +

- What "success" means is up to a given stream instance (or more precisely, its underlying sink) to decide. For - example, for a file stream it could simply mean that the OS has accepted the write, and not necessarily that the - chunk has been flushed to disk. Some streams might not be able to give such a signal at all, in which case the - returned promise will fulfill immediately. +

Various abstract operations performed here include object creation (often of +promises), which usually would require specifying a realm for the created object. However, because +of the locking, none of these objects can be observed by author code. As such, the realm used to +create them does not matter. + +

+ ReadableStreamTee(|stream|, + |cloneForBranch2|) is meant to be called from other specifications that wish to [=tee a + readable stream|tee=] a given readable stream. + + The second argument, |cloneForBranch2|, governs whether or not the data from the original stream + will be cloned (using HTML's [=serializable objects=] framework) before appearing in the second of + the returned branches. This is useful for scenarios where both branches are to be consumed in such + a way that they might otherwise interfere with each other, such as by [=transferable + objects|transferring=] their [=chunks=]. However, it does introduce a noticeable asymmetry between + the two branches, and limits the possible [=chunks=] to serializable ones. [[!HTML]] + +

In this standard ReadableStreamTee is always called with |cloneForBranch2| set to + false; other specifications pass true. + + It performs the following steps: + + 1. Assert: |stream| [=implements=] {{ReadableStream}}. + 1. Assert: |cloneForBranch2| is a boolean. + 1. Let |reader| be ? [$AcquireReadableStreamDefaultReader$](|stream|). + 1. Let |reading| be false. + 1. Let |canceled1| be false. + 1. Let |canceled2| be false. + 1. Let |reason1| be undefined. + 1. Let |reason2| be undefined. + 1. Let |branch1| be undefined. + 1. Let |branch2| be undefined. + 1. Let |cancelPromise| be [=a new promise=]. + 1. Let |pullAlgorithm| be the following steps: + 1. If |reading| is true, return [=a promise resolved with=] undefined. + 1. Set |reading| to true. + 1. Let |readPromise| be the result of [=reacting=] to ! + [$ReadableStreamDefaultReaderRead$](|reader|) with the following fulfillment steps given the + argument |result|: + 1. Set |reading| to false. + 1. Assert: [$Type$](|result|) is Object. + 1. Let |done| be ! [$Get$](|result|, "`done`"). + 1. Assert: [$Type$](|done|) is Boolean. + 1. If |done| is true, + 1. If |canceled1| is false, + 1. Perform ! [$ReadableStreamDefaultControllerClose$](|branch1|.\[[readableStreamController]]). + 1. If |canceled2| is false, + 1. Perform ! + [$ReadableStreamDefaultControllerClose$](|branch2|.\[[readableStreamController]]). + 1. Return. + 1. Let |value| be ! [$Get$](|result|, "`value`"). + 1. Let |value1| and |value2| be |value|. + 1. If |canceled2| is false and |cloneForBranch2| is true, set |value2| to ? + [$StructuredDeserialize$](? [$StructuredSerialize$](|value2|), [=the current Realm=]). + 1. If |canceled1| is false, perform ? + [$ReadableStreamDefaultControllerEnqueue$](|branch1|.\[[readableStreamController]], + |value1|). + 1. If |canceled2| is false, perform ? + ReadableStreamDefaultControllerEnqueue(|branch2|.\[[readableStreamController]], |value2|). + 1. Set |readPromise|.\[[PromiseIsHandled]] to true. + 1. Return [=a promise resolved with=] undefined. + 1. Let |cancel1Algorithm| be the following steps, taking a |reason| argument: + 1. Set |canceled1| to true. + 1. Set |reason1| to |reason|. + 1. If |canceled2| is true, + 1. Let |compositeReason| be ! [$CreateArrayFromList$](« |reason1|, |reason2| »). + 1. Let |cancelResult| be ! [$ReadableStreamCancel$](|stream|, |compositeReason|). + 1. [=Resolve=] |cancelPromise| with |cancelResult|. + 1. Return |cancelPromise|. + 1. Let |cancel2Algorithm| be the following steps, taking a |reason| argument: + 1. Set |canceled2| to true. + 1. Set |reason2| to |reason|. + 1. If |canceled1| is true, + 1. Let |compositeReason| be ! [$CreateArrayFromList$](« |reason1|, |reason2| »). + 1. Let |cancelResult| be ! [$ReadableStreamCancel$](|stream|, |compositeReason|). + 1. [=Resolve=] |cancelPromise| with |cancelResult|. + 1. Return |cancelPromise|. + 1. Let |startAlgorithm| be an algorithm that returns undefined. + 1. Set |branch1| to ! [$CreateReadableStream$](|startAlgorithm|, |pullAlgorithm|, + |cancel1Algorithm|). + 1. Set |branch2| to ! [$CreateReadableStream$](|startAlgorithm|, |pullAlgorithm|, + |cancel2Algorithm|). + 1. [=Upon rejection=] of |reader|.\[[closedPromise]] with reason |r|, + 1. Perform ! [$ReadableStreamDefaultControllerError$](|branch1|.\[[readableStreamController]], + |r|). + 1. Perform ! [$ReadableStreamDefaultControllerError$](|branch2|.\[[readableStreamController]], + |r|). + 1. Return « |branch1|, |branch2| ».

-
- The {{WritableStreamDefaultWriter/desiredSize}} and {{WritableStreamDefaultWriter/ready}} properties of writable - stream writers allow producers to more precisely respond to flow control signals from the stream, to keep - memory usage below the stream's specified high water mark. The following example writes an infinite sequence of - random bytes to a stream, using {{WritableStreamDefaultWriter/desiredSize}} to determine how many bytes to generate at - a given time, and using {{WritableStreamDefaultWriter/ready}} to wait for the backpressure to subside. +

Interfacing with controllers

-

-  async function writeRandomBytesForever(writableStream) {
-    const writer = writableStream.getWriter();
+In terms of specification factoring, the way that the {{ReadableStream}} class encapsulates the
+behavior of both simple readable streams and [=readable byte streams=] into a single class is by
+centralizing most of the potentially-varying logic inside the two controller classes,
+{{ReadableStreamDefaultController}} and {{ReadableByteStreamController}}. Those classes define most
+of the stateful internal slots and abstract operations for how a stream's [=internal queue=] is
+managed and how it interfaces with its [=underlying source=] or [=underlying byte source=].
 
-    while (true) {
-      await writer.ready;
+Each controller class defines two internal methods, which are called by the {{ReadableStream}}
+algorithms:
 
-      const bytes = new Uint8Array(writer.desiredSize);
-      crypto.getRandomValues(bytes);
+
+
\[[CancelSteps]](reason) +
The controller's steps that run in reaction to the stream being [=cancel a readable + stream|canceled=], used to clean up the state stored in the controller and inform the + [=underlying source=]. + +
\[[PullSteps]]() +
The controller's steps that run when a [=default reader=] is read from, used to pull from the + controller any queued [=chunks=], or pull from the [=underlying source=] to get more chunks. +
- // Purposefully don't await; awaiting writer.ready is enough. - writer.write(bytes).catch(() => {}); - } - } +(These are defined as internal methods, instead of as abstract operations, so that they can be +called polymorphically by the {{ReadableStream}} algorithms, without having to branch on which type +of controller is present.) + +The rest of this section concerns abstract operations that go in the other direction: they are +used by the controller implementations to affect their associated {{ReadableStream}} object. This +translates internal state changes of the controller into developer-facing results visible through +the {{ReadableStream}}'s public API. + +
+ ReadableStreamAddReadIntoRequest(|stream|) + performs the following steps: + + 1. Assert: |stream|.\[[reader]] [=implements=] {{ReadableStreamBYOBReader}}. + 1. Assert: |stream|.\[[state]] is "`readable"` or `"closed`". + 1. Let |promise| be [=a new promise=]. + 1. [=list/Append=] |promise| to |stream|.\[[reader]].\[[readIntoRequests]]. + 1. Return |promise|. +
- writeRandomBytesForever(myWritableStream).catch(e => console.error("Something broke", e)); -
+
+ ReadableStreamAddReadRequest(|stream|) + performs the following steps: - Note how we don't await the promise returned by {{WritableStreamDefaultWriter/write()}}; this would be - redundant with awaiting the {{WritableStreamDefaultWriter/ready}} promise. Additionally, similar to a previous example, we use the .catch(() => {}) pattern on the - promises returned by {{WritableStreamDefaultWriter/write()}}; in this case we'll be notified about any failures - awaiting the {{WritableStreamDefaultWriter/ready}} promise. + 1. Assert: |stream|.\[[reader]] [=implements=] {{ReadableStreamDefaultReader}}. + 1. Assert: |stream|.\[[state]] is "`readable"`. + 1. Let |promise| be [=a new promise=]. + 1. [=list/Append=] |promise| to |stream|.\[[reader]].\[[readRequests]]. + 1. Return |promise|.
-
- To further emphasize how it's a bad idea to await the promise returned by - {{WritableStreamDefaultWriter/write()}}, consider a modification of the above example, where we continue to use the - {{WritableStreamDefaultWriter}} interface directly, but we don't control how many bytes we have to write at a given - time. In that case, the backpressure-respecting code looks the same: +
+ ReadableStreamCancel(|stream|, |reason|) performs the following steps: + + 1. Set |stream|.\[[disturbed]] to true. + 1. If |stream|.\[[state]] is "`closed`", return [=a promise resolved with=] undefined. + 1. If |stream|.\[[state]] is "`errored`", return [=a promise rejected with=] + |stream|.\[[storedError]]. + 1. Perform ! [$ReadableStreamClose$](|stream|). + 1. Let |sourceCancelPromise| be ! + |stream|.\[[readableStreamController]].[$ReadableStreamController/[[CancelSteps]]$](|reason|). + 1. Return the result of [=reacting=] to |sourceCancelPromise| with a fulfillment step that returns + undefined. +
-

-  async function writeSuppliedBytesForever(writableStream, getBytes) {
-    const writer = writableStream.getWriter();
+
+ ReadableStreamClose(|stream|) performs the following steps: + + 1. Assert: |stream|.\[[state]] is "`readable`". + 1. Set |stream|.\[[state]] to "`closed`". + 1. Let |reader| be |stream|.\[[reader]]. + 1. If |reader| is undefined, return. + 1. If |reader| [=implements=] {{ReadableStreamDefaultReader}}, + 1. [=list/For each=] |readRequest| of |reader|.\[[readRequests]], + 1. [=Resolve=] |readRequest| with ! [$ReadableStreamCreateReadResult$](undefined, true, + |reader|.\[[forAuthorCode]]). + 1. Set |reader|.\[[readRequests]] to an empty [=list=]. + 1. [=Resolve=] |reader|.\[[closedPromise]] with undefined. + +

The case where |stream|.\[[state]] is "`closed`", but |stream|.\[[closeRequested]] + is false, will happen if the stream was closed without its controller's close method ever being + called: i.e., if the stream was closed by a call to {{ReadableStream/cancel(reason)}}. In this + case we allow the controller's close() method to be called and silently do nothing, + since the cancelation was outside the control of the underlying source. +

- while (true) { - await writer.ready; +
+ ReadableStreamCreateReadResult(|value|, |done|, + |forAuthorCode|) performs the following steps: + + 1. Let |prototype| be null. + 1. If |forAuthorCode| is true, set |prototype| to {{%ObjectPrototype%}}. + 1. Assert: [$Type$](|done|) is Boolean. + 1. Let |obj| be [$OrdinaryObjectCreate$](|prototype|). + 1. Perform [$CreateDataProperty$](|obj|, "`value`", |value|). + 1. Perform [$CreateDataProperty$](|obj|, "`done`", |done|). + 1. Return |obj|. + +
+ When |forAuthorCode| is true, this abstract operation gives the same result as + [$CreateIterResultObject$](|value|, |done|). This provides the expected semantics when the object + is to be returned from the {{ReadableStreamDefaultReader/read()|defaultReader.read()}} or + {{ReadableStreamBYOBReader/read()|byobReader.read()}} methods. - const bytes = getBytes(); - writer.write(bytes).catch(() => {}); - } - } -
+ However, resolving promises with such objects will unavoidably result in an access to + Object.prototype.then. For internal use, particularly in {{ReadableStream/pipeTo()}} + and in other specifications, it is important that reads not be observable by author code—even if + that author code has tampered with + Object.prototype. For this reason, a false value of |forAuthorCode| results in an + object with a null prototype, keeping promise resolution unobservable. + + The underlying issue here is that reading from streams always uses promises for { value, + done } objects, even in specifications. Although it is conceivable we could rephrase all + of the internal algorithms to not use promises and not use JavaScript objects, and instead only + package up the results into promise-for-{ value, done } when a read() + method is called, this would be a large undertaking, which we have not done. See + whatwg/infra#181 for more background on + this subject. +
+
- Unlike the previous example, where—because we were always writing exactly - {{WritableStreamDefaultWriter/desiredSize|writer.desiredSize}} bytes each time—the - {{WritableStreamDefaultWriter/write()}} and {{WritableStreamDefaultWriter/ready}} promises were synchronized, in this - case it's quite possible that the {{WritableStreamDefaultWriter/ready}} promise fulfills before the one returned by - {{WritableStreamDefaultWriter/write()}} does. Remember, the {{WritableStreamDefaultWriter/ready}} promise fulfills - when the desired size becomes positive, which might be - before the write succeeds (especially in cases with a larger high water mark). +
+ ReadableStreamError(|stream|, + |e|) performs the following steps: + + 1. Assert: |stream|.\[[state]] is "`readable`". + 1. Set |stream|.\[[state]] to "`errored`". + 1. Set |stream|.\[[storedError]] to |e|. + 1. Let |reader| be |stream|.\[[reader]]. + 1. If |reader| is undefined, return. + 1. If |reader| [=implements=] {{ReadableStreamDefaultReader}}, + 1. [=list/For each=] |readRequest| of |reader|.\[[readRequests]], + 1. [=Reject=] |readRequest| with |e|. + 1. Set |reader|.\[[readRequests]] to a new empty [=list=]. + 1. Otherwise, + 1. Assert: |reader| [=implements=] {{ReadableStreamBYOBReader}}. + 1. [=list/For each=] |readIntoRequest| of |reader|.\[[readIntoRequests]], + 1. [=Reject=] |readIntoRequest| with |e|. + 1. Set |reader|.\[[readIntoRequests]] to a new empty [=list=]. + 1. [=Reject=] |reader|.\[[closedPromise]] with |e|. + 1. Set |reader|.\[[closedPromise]].\[[PromiseIsHandled]] to true. +
- In other words, awaiting the return value of {{WritableStreamDefaultWriter/write()}} means you never - queue up writes in the stream's internal queue, instead only executing a write after the previous one succeeds, - which can result in low throughput. +
+ ReadableStreamFulfillReadIntoRequest(|stream|, + |chunk|, |done|) performs the following steps: + + 1. Let |reader| be |stream|.\[[reader]]. + 1. Assert: |reader|.\[[readIntoRequests]] is not [=list/is empty|empty=]. + 1. Let |readIntoRequest| be |reader|.\[[readIntoRequests]][0]. + 1. [=list/Remove=] |readIntoRequest| from |reader|.\[[readIntoRequests]]. + 1. [=Resolve=] |readIntoRequest| with ! [$ReadableStreamCreateReadResult$](|chunk|, + |done|, |reader|.\[[forAuthorCode]]).
-

Class WritableStream

+
+ ReadableStreamFulfillReadRequest(|stream|, |chunk|, + |done|) performs the following steps: + + 1. Let |reader| be |stream|.\[[reader]]. + 1. Assert: |reader|.\[[readRequests]] is not [=list/is empty|empty=]. + 1. Let |readRequest| be |reader|.\[[readRequests]][0]. + 1. [=list/Remove=] |readRequest| from |reader|.\[[readRequests]]. + 1. [=Resolve=] |readRequest| with ! [$ReadableStreamCreateReadResult$](|chunk|, |done|, + |reader|.\[[forAuthorCode]]). +
-

Class definition

+
+ ReadableStreamGetNumReadIntoRequests(|stream|) + performs the following steps: -
+ 1. Return |stream|.\[[reader]].\[[readIntoRequests]]'s [=list/size=]. +
-This section is non-normative. +
+ ReadableStreamGetNumReadRequests(|stream|) + performs the following steps: -If one were to write the {{WritableStream}} class in something close to the syntax of [[!ECMASCRIPT]], it would look -like + 1. Return |stream|.\[[reader]].\[[readRequests]]'s [=list/size=]. +
-

-  class WritableStream {
-    constructor(underlyingSink = {}, strategy = {})
+
+ ReadableStreamHasBYOBReader(|stream|) performs the + following steps: - get locked() + 1. Let |reader| be |stream|.\[[reader]]. + 1. If |reader| is undefined, return false. + 1. If |reader| [=implements=] {{ReadableStreamBYOBReader}}, return true. + 1. Return false. +
- abort(reason) - close() - getWriter() - } -
+
+ ReadableStreamHasDefaultReader(|stream|) performs the + following steps: + 1. Let |reader| be |stream|.\[[reader]]. + 1. If |reader| is undefined, return false. + 1. If |reader| [=implements=] {{ReadableStreamDefaultReader}}, return true. + 1. Return false.
-

Internal slots

+

Readers

-Instances of {{WritableStream}} are created with the internal slots described in the following table: +The following abstract operations support the implementation and manipulation of +{{ReadableStreamDefaultReader}} and {{ReadableStreamBYOBReader}} instances. - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Internal SlotDescription (non-normative)
\[[backpressure]] - The backpressure signal set by the controller -
\[[closeRequest]] - The promise returned from the writer {{WritableStreamDefaultWriter/close()}} method -
\[[inFlightWriteRequest]] - A slot set to the promise for the current in-flight write operation while the - underlying sink's write algorithm is executing and has not yet fulfilled, used to prevent reentrant calls -
\[[inFlightCloseRequest]] - A slot set to the promise for the current in-flight close operation while the - underlying sink's close algorithm is executing and has not yet fulfilled, used to prevent the - {{WritableStreamDefaultWriter/abort()}} method from interrupting close -
\[[pendingAbortRequest]] - A Record containing the promise returned from - {{WritableStreamDefaultWriter/abort()}} and the reason passed to - {{WritableStreamDefaultWriter/abort()}} -
\[[state]] - A string containing the stream's current state, used internally; one of - "writable", "closed", "erroring", or "errored" -
\[[storedError]] - A value indicating how the stream failed, to be given as a failure reason or exception - when trying to operate on the stream while in the "errored" state -
\[[writableStreamController]] - A {{WritableStreamDefaultController}} created with the ability to control the state and - queue of this stream; also used for the IsWritableStream brand check -
\[[writer]] - A {{WritableStreamDefaultWriter}} instance, if the stream is locked to a writer, or - undefined if it is not -
\[[writeRequests]] - A List of promises representing the stream's internal queue of write requests not yet - processed by the underlying sink -
+
+ ReadableStreamReaderGenericCancel(|reader|, + |reason|) performs the following steps: -

- The \[[inFlightCloseRequest]] slot and \[[closeRequest]] slot are mutually exclusive. Similarly, no element will be - removed from \[[writeRequests]] while \[[inFlightWriteRequest]] is not undefined. Implementations - can optimize storage for these slots based on these invariants. -

+ 1. Let |stream| be |reader|.\[[ownerReadableStream]]. + 1. Assert: |stream| is not undefined. + 1. Return ! [$ReadableStreamCancel$](|stream|, |reason|). +
-

new -WritableStream(underlyingSink = {}, strategy = {})

+
+ ReadableStreamReaderGenericInitialize(|reader|, + |stream|) performs the following steps: + + 1. Set |reader|.\[[forAuthorCode]] to true. + 1. Set |reader|.\[[ownerReadableStream]] to |stream|. + 1. Set |stream|.\[[reader]] to |reader|. + 1. If |stream|.\[[state]] is "`readable`", + 1. Set |reader|.\[[closedPromise]] to [=a new promise=]. + 1. Otherwise, if |stream|.\[[state]] is "`closed`", + 1. Set |reader|.\[[closedPromise]] to [=a promise resolved with=] undefined. + 1. Otherwise, + 1. Assert: |stream|.\[[state]] is "`errored`". + 1. Set |reader|.\[[closedPromise]] to [=a promise rejected with=] |stream|.\[[storedError]]. + 1. Set |reader|.\[[closedPromise]].\[[PromiseIsHandled]] to true. +
-
- The underlyingSink argument represents the underlying sink, as described in - [[#underlying-sink-api]]. - - The strategy argument represents the stream's queuing strategy, as described in [[#qs-api]]. If it - is not provided, the default behavior will be the same as a {{CountQueuingStrategy}} with a high water mark - of 1. -
- - - 1. Perform ! InitializeWritableStream(_this_). - 1. Let _size_ be ? GetV(_strategy_, `"size"`). - 1. Let _highWaterMark_ be ? GetV(_strategy_, `"highWaterMark"`). - 1. Let _type_ be ? GetV(_underlyingSink_, `"type"`). - 1. If _type_ is not *undefined*, throw a *RangeError* exception.

This is to allow us to add new - potential types in the future, without backward-compatibility concerns.

- 1. Let _sizeAlgorithm_ be ? MakeSizeAlgorithmFromSizeFunction(_size_). - 1. If _highWaterMark_ is *undefined*, let _highWaterMark_ be *1*. - 1. Set _highWaterMark_ to ? ValidateAndNormalizeHighWaterMark(_highWaterMark_). - 1. Perform ? SetUpWritableStreamDefaultControllerFromUnderlyingSink(*this*, _underlyingSink_, _highWaterMark_, - _sizeAlgorithm_). -
- -

Underlying sink API

+
+ ReadableStreamReaderGenericRelease(|reader|) + performs the following steps: + + 1. Assert: |reader|.\[[ownerReadableStream]] is not undefined. + 1. Assert: |reader|.\[[ownerReadableStream]].\[[reader]] is |reader|. + 1. If |reader|.\[[ownerReadableStream]].\[[state]] is "`readable`", [=reject=] + |reader|.\[[closedPromise]] with a {{TypeError}} exception. + 1. Otherwise, set |reader|.\[[closedPromise]] to [=a promise rejected with=] a {{TypeError}} + exception. + 1. Set |reader|.\[[closedPromise]].\[[PromiseIsHandled]] to true. + 1. Set |reader|.\[[ownerReadableStream]].\[[reader]] to undefined. + 1. Set |reader|.\[[ownerReadableStream]] to undefined. +
-
+
+ ReadableStreamBYOBReaderRead(|reader|, |view|) performs + the following steps: + + 1. Let |stream| be |reader|.\[[ownerReadableStream]]. + 1. Assert: |stream| is not undefined. + 1. Set |stream|.\[[disturbed]] to true. + 1. If |stream|.\[[state]] is "`errored`", return [=a promise rejected with=] + |stream|.\[[storedError]]. + 1. Return ! [$ReadableByteStreamControllerPullInto$](|stream|.\[[readableStreamController]], + |view|). +
-This section is non-normative. +
+ ReadableStreamDefaultReaderRead(|reader|) performs + the following steps: + + 1. Let |stream| be |reader|.\[[ownerReadableStream]]. + 1. Assert: |stream| is not undefined. + 1. Set |stream|.\[[disturbed]] to true. + 1. If |stream|.\[[state]] is "`closed`", return [=a promise resolved with=] ! + [$ReadableStreamCreateReadResult$](undefined, true, |reader|.\[[forAuthorCode]]). + 1. If |stream|.\[[state]] is "`errored`", return [=a promise rejected with=] + |stream|.\[[storedError]]. + 1. Assert: |stream|.\[[state]] is "`readable`". + 1. Return ! |stream|.\[[readableStreamController]].[$ReadableStreamController/[[PullSteps]]$](). +
-The {{WritableStream()}} constructor accepts as its first argument a JavaScript object representing the underlying -sink. Such objects can contain any of the following properties: +
+ SetUpReadableStreamBYOBReader(|reader|, |stream|) + performs the following steps: -
-
start(controller)
-
-

A function that is called immediately during creation of the {{WritableStream}}.

+ 1. If ! [$IsReadableStreamLocked$](|stream|) is true, throw a {{TypeError}} exception. + 1. If |stream|.\[[readableStreamController]] does not [=implement=] + {{ReadableByteStreamController}}, throw a {{TypeError}} exception. + 1. Perform ! [$ReadableStreamReaderGenericInitialize$](|reader|, |stream|). + 1. Set |reader|.\[[readIntoRequests]] to a new empty [=list=]. +
-

Typically this is used to acquire access to the underlying sink resource being represented.

+
+ SetUpReadableStreamDefaultReader(|reader|, + |stream|) performs the following steps: -

If this setup process is asynchronous, it can return a promise to signal success or failure; a rejected promise - will error the stream. Any thrown exceptions will be re-thrown by the {{WritableStream()}} constructor.

- + 1. If ! [$IsReadableStreamLocked$](|stream|) is true, throw a {{TypeError}} exception. + 1. Perform ! [$ReadableStreamReaderGenericInitialize$](|reader|, |stream|). + 1. Set |reader|.\[[readRequests]] to a new empty [=list=]. +
-
write(chunk, controller)
-
-

A function that is called when a new chunk of data is ready to be written to the underlying sink. - The stream implementation guarantees that this function will be called only after previous writes have succeeded, - and never before {{underlying sink/start()}} has succeeded or after {{underlying sink/close()}} or - {{underlying sink/abort()}} have been called.

- -

This function is used to actually send the data to the resource presented by the underlying sink, for - example by calling a lower-level API.

- -

If the process of writing data is asynchronous, and communicates success or failure signals back to its user, - then this function can return a promise to signal success or failure. This promise return value will be communicated - back to the caller of {{WritableStreamDefaultWriter/write()|writer.write()}}, so they can monitor that individual - write. Throwing an exception is treated the same as returning a rejected promise.

- -

Note that such signals are not always available; compare e.g. [[#example-ws-no-backpressure]] with - [[#example-ws-backpressure]]. In such cases, it's best to not return anything.

- -

The promise potentially returned by this function also governs whether the given chunk counts as written for the - purposes of computed the desired size to fill the stream's - internal queue. That is, during the time it takes the promise to settle, - {{WritableStreamDefaultWriter/desiredSize|writer.desiredSize}} will stay at its previous value, only increasing to - signal the desire for more chunks once the write succeeds.

-
- -
close()
-
-

A function that is called after the producer signals, via - {{WritableStreamDefaultWriter/close()|writer.close()}}, that they are done writing chunks to the stream, and - subsequently all queued-up writes have successfully completed.

+

Default controllers

+ +The following abstract operations support the implementation of the +{{ReadableStreamDefaultController}} class. + +
+ ReadableStreamDefaultControllerCallPullIfNeeded(|controller|) + performs the following steps: + + 1. Let |shouldPull| be ! [$ReadableStreamDefaultControllerShouldCallPull$](|controller|). + 1. If |shouldPull| is false, return. + 1. If |controller|.\[[pulling]] is true, + 1. Set |controller|.\[[pullAgain]] to true. + 1. Return. + 1. Assert: |controller|.\[[pullAgain]] is false. + 1. Set |controller|.\[[pulling]] to true. + 1. Let |pullPromise| be the result of performing |controller|.\[[pullAlgorithm]]. + 1. [=Upon fulfillment=] of |pullPromise|, + 1. Set |controller|.\[[pulling]] to false. + 1. If |controller|.\[[pullAgain]] is true, + 1. Set |controller|.\[[pullAgain]] to false. + 1. Perform ! [$ReadableStreamDefaultControllerCallPullIfNeeded$](|controller|). + 1. [=Upon rejection=] of |pullPromise| with reason |e|, + 1. Perform ! [$ReadableStreamDefaultControllerError$](|controller|, |e|). +
-

This function can perform any actions necessary to finalize or flush writes to the underlying sink, and - release access to any held resources.

+
+ ReadableStreamDefaultControllerShouldCallPull(|controller|) + performs the following steps: + + 1. Let |stream| be |controller|.\[[controlledReadableStream]]. + 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$](|controller|) is false, return false. + 1. If |controller|.\[[started]] is false, return false. + 1. If ! [$IsReadableStreamLocked$](|stream|) is true and ! + [$ReadableStreamGetNumReadRequests$](|stream|) > 0, return true. + 1. Let |desiredSize| be ! [$ReadableStreamDefaultControllerGetDesiredSize$](|controller|). + 1. Assert: |desiredSize| is not null. + 1. If |desiredSize| > 0, return true. + 1. Return false. +
-

If the shutdown process is asynchronous, the function can return a promise to signal success or failure; the - result will be communicated via the return value of the called - {{WritableStreamDefaultWriter/close()|writer.close()}} method. Additionally, a rejected promise will error the - stream, instead of letting it close successfully. Throwing an exception is treated the same as returning a rejected - promise.

-
+
+ ReadableStreamDefaultControllerClearAlgorithms(|controller|) + is called once the stream is closed or errored and the algorithms will not be executed any more. By + removing the algorithm references it permits the [=underlying source=] object to be garbage + collected even if the {{ReadableStream}} itself is still referenced. -
abort(reason)
-
-

A function that is called after the producer signals, via {{WritableStream/abort()|stream.abort()}} or - {{WritableStreamDefaultWriter/abort()|writer.abort()}}, that they wish to abort - the stream. It takes as its argument the same value as was passed to those methods by the producer.

+

This is observable using weak + references. See tc39/proposal-weakrefs#31 for more + detail. -

Writable streams can additionally be aborted under certain conditions during piping; see the definition - of the {{ReadableStream/pipeTo()}} method for more details.

+ It performs the following steps: -

This function can clean up any held resources, much like {{underlying sink/close()}}, but perhaps with some - custom handling.

+ 1. Set |controller|.\[[pullAlgorithm]] to undefined. + 1. Set |controller|.\[[cancelAlgorithm]] to undefined. + 1. Set |controller|.\[[strategySizeAlgorithm]] to undefined. +
-

If the shutdown process is asynchronous, the function can return a promise to signal success or failure; the - result will be communicated via the return value of the called abort() method. Throwing an exception is - treated the same as returning a rejected promise. Regardless, the stream will be errored with a new {{TypeError}} - indicating that it was aborted.

- - +
+ ReadableStreamDefaultControllerClose(|controller|) can be called by other + specifications that wish to close a readable stream, in the same way a developer-created stream + would be closed by its associated controller object. Specifications should not do this to + streams or controllers they did not create. + + It performs the following steps: + + 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$](|controller|) is false, return. + 1. Let |stream| be |controller|.\[[controlledReadableStream]]. + 1. Set |controller|.\[[closeRequested]] to true. + 1. If |controller|.\[[queue]] [=list/is empty=], + 1. Perform ! [$ReadableStreamDefaultControllerClearAlgorithms$](|controller|). + 1. Perform ! [$ReadableStreamClose$](|stream|). +
-The controller argument passed to {{underlying sink/start()}} and {{underlying sink/write()}} is an -instance of {{WritableStreamDefaultController}}, and has the ability to error the stream. This is mainly used for -bridging the gap with non-promise-based APIs, as seen for example in [[#example-ws-no-backpressure]]. +
+ ReadableStreamDefaultControllerEnqueue(|controller|, |chunk|) can be called by other + specifications that wish to enqueue [=chunks=] in a readable stream, in the same way a developer + would enqueue chunks using the stream's associated controller object. Specifications should + not do this to streams or controllers they did not create. + + It performs the following steps: + + 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$](|controller|) is false, return. + 1. Let |stream| be |controller|.\[[controlledReadableStream]]. + 1. If ! [$IsReadableStreamLocked$](|stream|) is true and ! + [$ReadableStreamGetNumReadRequests$](|stream|) > 0, perform ! + [$ReadableStreamFulfillReadRequest$](|stream|, |chunk|, false). + 1. Otherwise, + 1. Let |result| be the result of performing |controller|.\[[strategySizeAlgorithm]], passing in + |chunk|, and interpreting the result as a [=completion record=]. + 1. If |result| is an abrupt completion, + 1. Perform ! [$ReadableStreamDefaultControllerError$](|controller|, |result|.\[[Value]]). + 1. Return |result|. + 1. Let |chunkSize| be |result|.\[[Value]]. + 1. Let |enqueueResult| be [$EnqueueValueWithSize$](|controller|, |chunk|, |chunkSize|). + 1. If |enqueueResult| is an abrupt completion, + 1. Perform ! [$ReadableStreamDefaultControllerError$](|controller|, |enqueueResult|.\[[Value]]). + 1. Return |enqueueResult|. + 1. Perform ! [$ReadableStreamDefaultControllerCallPullIfNeeded$](|controller|). +
+
+ ReadableStreamDefaultControllerError(|controller|, |e|) can be called by other + specifications that wish to move a readable stream to an errored state, in the same way a + developer would error a stream using its associated controller object. Specifications should + not do this to streams or controllers they did not create. + + It performs the following steps: + + 1. Let |stream| be |controller|.\[[controlledReadableStream]]. + 1. If |stream|.\[[state]] is not "`readable`", return. + 1. Perform ! [$ResetQueue$](|controller|). + 1. Perform ! [$ReadableStreamDefaultControllerClearAlgorithms$](|controller|). + 1. Perform ! [$ReadableStreamError$](|stream|, |e|).
-

Properties of the {{WritableStream}} prototype

+
+ ReadableStreamDefaultControllerGetDesiredSize(|controller|) can be called by other + specifications that wish to determine the [=desired size to fill a stream's internal queue|desired + size to fill this stream's internal queue=], similar to how a developer would consult the + {{ReadableStreamDefaultController/desiredSize}} property of the stream's associated controller + object. Specifications should not use this on streams or controllers they did not create. + + It performs the following steps: + + 1. Let |stream| be |controller|.\[[controlledReadableStream]]. + 1. Let |state| be |stream|.\[[state]]. + 1. If |state| is "`errored`", return null. + 1. If |state| is "`closed`", return 0. + 1. Return |controller|.\[[strategyHWM]] − |controller|.\[[queueTotalSize]]. +
-
get locked
+
+ ReadableStreamDefaultControllerHasBackpressure(|controller|) + is used in the implementation of {{TransformStream}}. It performs the following steps: -
- The locked getter returns whether or not the writable stream is locked to a writer. + 1. If ! [$ReadableStreamDefaultControllerShouldCallPull$](|controller|) is true, return false. + 1. Otherwise, return true. +
+ +
+ ReadableStreamDefaultControllerCanCloseOrEnqueue(|controller|) + performs the following steps: + + 1. Let |state| be |controller|.\[[controlledReadableStream]].\[[state]]. + 1. If |controller|.\[[closeRequested]] is false and |state| is "`readable`", return true. + 1. Otherwise, return false. + +

The case where |controller|.\[[closeRequested]] is false, but |state| is not + "`readable`", happens when the stream is errored via + {{ReadableStreamDefaultController/error(e)|controller.error()}}, or when it is closed without its + controller's {{ReadableStreamDefaultController/close()|controller.close()}} method ever being + called: e.g., if the stream was closed by a call to + {{ReadableStream/cancel(reason)|stream.cancel()}}.

- - 1. If ! IsWritableStream(*this*) is *false*, throw a *TypeError* exception. - 1. Return ! IsWritableStreamLocked(*this*). - +
+ SetUpReadableStreamDefaultController(|stream|, + |controller|, |startAlgorithm|, |pullAlgorithm|, |cancelAlgorithm|, |highWaterMark|, + |sizeAlgorithm|) performs the following steps: + + 1. Assert: |stream|.\[[readableStreamController]] is undefined. + 1. Set |controller|.\[[controlledReadableStream]] to |stream|. + 1. Perform ! [$ResetQueue$](|controller|). + 1. Set |controller|.\[[started]], |controller|.\[[closeRequested]], |controller|.\[[pullAgain]], and + |controller|.\[[pulling]] to false. + 1. Set |controller|.\[[strategySizeAlgorithm]] to |sizeAlgorithm| and |controller|.\[[strategyHWM]] + to |highWaterMark|. + 1. Set |controller|.\[[pullAlgorithm]] to |pullAlgorithm|. + 1. Set |controller|.\[[cancelAlgorithm]] to |cancelAlgorithm|. + 1. Set |stream|.\[[readableStreamController]] to |controller|. + 1. Let |startResult| be the result of performing |startAlgorithm|. (This might throw an exception.) + 1. Let |startPromise| be [=a promise resolved with=] |startResult|. + 1. [=Upon fulfillment=] of |startPromise|, + 1. Set |controller|.\[[started]] to true. + 1. Assert: |controller|.\[[pulling]] is false. + 1. Assert: |controller|.\[[pullAgain]] is false. + 1. Perform ! [$ReadableStreamDefaultControllerCallPullIfNeeded$](|controller|). + 1. [=Upon rejection=] of |startPromise| with reason |r|, + 1. Perform ! [$ReadableStreamDefaultControllerError$](|controller|, |r|). +
-
abort(reason)
+
+ SetUpReadableStreamDefaultControllerFromUnderlyingSource(|stream|, + |controller|, |underlyingSource|, |underlyingSourceDict|, |highWaterMark|, |sizeAlgorithm|) + performs the following steps: + + 1. Let |controller| be a [=new=] {{ReadableStreamDefaultController}}. + 1. Let |startAlgorithm| be an algorithm that returns undefined. + 1. Let |pullAlgorithm| be an algorithm that returns [=a promise resolved with=] undefined. + 1. Let |cancelAlgorithm| be an algorithm that returns [=a promise resolved with=] undefined. + 1. If |underlyingSourceDict|["{{UnderlyingSource/start}}"] [=map/exists=], then set + |startAlgorithm| to an algorithm which returns the result of [=invoking=] + |underlyingSourceDict|["{{UnderlyingSource/start}}"] with argument list + « |controller| » and [=callback this value=] |underlyingSource|. + 1. If |underlyingSourceDict|["{{UnderlyingSource/pull}}"] [=map/exists=], then set + |pullAlgorithm| to an algorithm which returns the result of [=invoking=] + |underlyingSourceDict|["{{UnderlyingSource/pull}}"] with argument list + « |controller| » and [=callback this value=] |underlyingSource|. + 1. If |underlyingSourceDict|["{{UnderlyingSource/cancel}}"] [=map/exists=], then set + |cancelAlgorithm| to an algorithm which takes an argument |reason| and returns the result of + [=invoking=] |underlyingSourceDict|["{{UnderlyingSource/cancel}}"] with argument list + « |reason| » and [=callback this value=] |underlyingSource|. + 1. Perform ? [$SetUpReadableStreamDefaultController$](|stream|, |controller|, |startAlgorithm|, + |pullAlgorithm|, |cancelAlgorithm|, |highWaterMark|, |sizeAlgorithm|). +
-
- The abort method aborts the stream, signaling that the producer can - no longer successfully write to the stream and it is to be immediately moved to an errored state, with any queued-up - writes discarded. This will also execute any abort mechanism of the underlying sink. +

Byte stream controllers

+ +
+ ReadableByteStreamControllerCallPullIfNeeded(|controller|) + performs the following steps: + + 1. Let |shouldPull| be ! [$ReadableByteStreamControllerShouldCallPull$](|controller|). + 1. If |shouldPull| is false, return. + 1. If |controller|.\[[pulling]] is true, + 1. Set |controller|.\[[pullAgain]] to true. + 1. Return. + 1. Assert: |controller|.\[[pullAgain]] is false. + 1. Set |controller|.\[[pulling]] to true. + 1. Let |pullPromise| be the result of performing |controller|.\[[pullAlgorithm]]. + 1. [=Upon fulfillment=] of |pullPromise|, + 1. Set |controller|.\[[pulling]] to false. + 1. If |controller|.\[[pullAgain]] is true, + 1. Set |controller|.\[[pullAgain]] to false. + 1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$](|controller|). + 1. [=Upon rejection=] of |pullPromise| with reason |e|, + 1. Perform ! [$ReadableByteStreamControllerError$](|controller|, |e|).
- - 1. If ! IsWritableStream(*this*) is *false*, return a promise rejected with a *TypeError* exception. - 1. If ! IsWritableStreamLocked(*this*) is *true*, return a promise rejected with a *TypeError* exception. - 1. Return ! WritableStreamAbort(*this*, _reason_). - +
+ ReadableByteStreamControllerClearAlgorithms(|controller|) + is called once the stream is closed or errored and the algorithms will not be executed any more. By + removing the algorithm references it permits the [=underlying byte source=] object to be garbage + collected even if the {{ReadableStream}} itself is still referenced. -
close()
+

This is observable using weak + references. See tc39/proposal-weakrefs#31 for more + detail. -

- The close method closes the stream. The underlying sink will finish processing any - previously-written chunks, before invoking its close behavior. During this time any further attempts to write - will fail (without erroring the stream). + It performs the following steps: - The method returns a promise that is fulfilled with undefined if all remaining chunks are - successfully written and the stream successfully closes, or rejects if an error is encountered during this process. + 1. Set |controller|.\[[pullAlgorithm]] to undefined. + 1. Set |controller|.\[[cancelAlgorithm]] to undefined.
- - 1. If ! IsWritableStream(*this*) is *false*, return a promise rejected with a *TypeError* exception. - 1. If ! IsWritableStreamLocked(*this*) is *true*, return a promise rejected with a *TypeError* exception. - 1. If ! WritableStreamCloseQueuedOrInFlight(*this*) is *true*, return a promise rejected with a *TypeError* - exception. - 1. Return ! WritableStreamClose(*this*). - +
+ ReadableByteStreamControllerClearPendingPullIntos(|controller|) + performs the following steps: -
getWriter()
+ 1. Perform ! [$ReadableByteStreamControllerInvalidateBYOBRequest$](|controller|). + 1. Set |controller|.\[[pendingPullIntos]] to a new empty [=list=]. +
-
- The getWriter method creates a writer (an instance of {{WritableStreamDefaultWriter}}) and locks the stream to the new writer. While the stream is locked, no other writer can be - acquired until this one is released. - - This functionality is especially useful for creating abstractions that desire the ability to write to a stream without - interruption or interleaving. By getting a writer for the stream, you can ensure nobody else can write at the same - time, which would cause the resulting written data to be unpredictable and probably useless. -
- - - 1. If ! IsWritableStream(*this*) is *false*, throw a *TypeError* exception. - 1. Return ? AcquireWritableStreamDefaultWriter(*this*). - - -

General writable stream abstract operations

- -The following abstract operations, unlike most in this specification, are meant to be generally useful by other -specifications, instead of just being part of the implementation of this spec's classes. - -

AcquireWritableStreamDefaultWriter ( stream )

- - - 1. Return ? Construct(`WritableStreamDefaultWriter`, « _stream_ »). - - -

CreateWritableStream ( startAlgorithm, -writeAlgorithm, closeAlgorithm, abortAlgorithm [, highWaterMark [, -sizeAlgorithm ] ] )

- -This abstract operation is meant to be called from other specifications that wish to create {{WritableStream}} -instances. The writeAlgorithm, closeAlgorithm and abortAlgorithm algorithms must return -promises; if supplied, sizeAlgorithm must be an algorithm accepting chunk objects and returning a -number; and if supplied, highWaterMark must be a non-negative, non-NaN number. - -

CreateWritableStream throws an exception if and only if the supplied startAlgorithm -throws.

- - - 1. If _highWaterMark_ was not passed, set it to *1*. - 1. If _sizeAlgorithm_ was not passed, set it to an algorithm that returns *1*. - 1. Assert: ! IsNonNegativeNumber(_highWaterMark_) is *true*. - 1. Let _stream_ be ObjectCreate(the original value of `WritableStream`'s `prototype` property). - 1. Perform ! InitializeWritableStream(_stream_). - 1. Let _controller_ be ObjectCreate(the original value of `WritableStreamDefaultController`'s `prototype` - property). - 1. Perform ? SetUpWritableStreamDefaultController(_stream_, _controller_, _startAlgorithm_, _writeAlgorithm_, - _closeAlgorithm_, _abortAlgorithm_, _highWaterMark_, _sizeAlgorithm_). - 1. Return _stream_. - - -

InitializeWritableStream ( stream -)

- - - 1. Set _stream_.[[state]] to `"writable"`. - 1. Set _stream_.[[storedError]], _stream_.[[writer]], _stream_.[[writableStreamController]], - _stream_.[[inFlightWriteRequest]], _stream_.[[closeRequest]], _stream_.[[inFlightCloseRequest]] and - _stream_.[[pendingAbortRequest]] to *undefined*. - 1. Set _stream_.[[writeRequests]] to a new empty List. - 1. Set _stream_.[[backpressure]] to *false*. - - -

IsWritableStream ( x )

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

IsWritableStreamLocked ( stream -)

- -This abstract operation is meant to be called from other specifications that may wish to query whether or not a -writable stream is locked to a writer. - - - 1. Assert: ! IsWritableStream(_stream_) is *true*. - 1. If _stream_.[[writer]] is *undefined*, return *false*. - 1. Return *true*. - - -

WritableStreamAbort ( stream, -reason )

- - - 1. Let _state_ be _stream_.[[state]]. - 1. If _state_ is `"closed"` or `"errored"`, return a promise resolved with *undefined*. - 1. If _stream_.[[pendingAbortRequest]] is not *undefined*, return _stream_.[[pendingAbortRequest]].[[promise]]. - 1. Assert: _state_ is `"writable"` or `"erroring"`. - 1. Let _wasAlreadyErroring_ be *false*. - 1. If _state_ is `"erroring"`, - 1. Set _wasAlreadyErroring_ to *true*. - 1. Set _reason_ to *undefined*. - 1. Let _promise_ be a new promise. - 1. Set _stream_.[[pendingAbortRequest]] to Record {[[promise]]: _promise_, [[reason]]: _reason_, - [[wasAlreadyErroring]]: _wasAlreadyErroring_}. - 1. If _wasAlreadyErroring_ is *false*, perform ! WritableStreamStartErroring(_stream_, _reason_). - 1. Return _promise_. - - -

WritableStreamClose ( stream )

- - - 1. Let _state_ be _stream_.[[state]]. - 1. If _state_ is `"closed"` or `"errored"`, return a promise rejected with a *TypeError* exception. - 1. Assert: _state_ is `"writable"` or `"erroring"`. - 1. Assert: ! WritableStreamCloseQueuedOrInFlight(_stream_) is *false*. - 1. Let _promise_ be a new promise. - 1. Set _stream_.[[closeRequest]] to _promise_. - 1. Let _writer_ be _stream_.[[writer]]. - 1. If _writer_ is not *undefined*, and _stream_.[[backpressure]] is *true*, and _state_ is `"writable"`, - resolve _writer_.[[readyPromise]] with *undefined*. - 1. Perform ! WritableStreamDefaultControllerClose(_stream_.[[writableStreamController]]). - 1. Return _promise_. - - -

Writable stream abstract operations used by controllers

- -To allow future flexibility to add different writable stream behaviors (similar to the distinction between default -readable streams and readable byte streams), much of the internal state of a writable stream is -encapsulated by the {{WritableStreamDefaultController}} class. - -The abstract operations in this section are interfaces that are used by the controller implementation to affect its -associated {{WritableStream}} object, translating the controller's internal state changes into developer-facing results -visible through the {{WritableStream}}'s public API. - -

WritableStreamAddWriteRequest ( -stream )

- - - 1. Assert: ! IsWritableStreamLocked(_stream_) is *true*. - 1. Assert: _stream_.[[state]] is `"writable"`. - 1. Let _promise_ be a new promise. - 1. Append _promise_ as the last element of _stream_.[[writeRequests]]. - 1. Return _promise_. - - -

WritableStreamDealWithRejection ( stream, error )

- - - 1. Let _state_ be _stream_.[[state]]. - 1. If _state_ is `"writable"`, - 1. Perform ! WritableStreamStartErroring(_stream_, _error_). - 1. Return. - 1. Assert: _state_ is `"erroring"`. - 1. Perform ! WritableStreamFinishErroring(_stream_). - - -

WritableStreamStartErroring ( -stream, reason )

- - - 1. Assert: _stream_.[[storedError]] is *undefined*. - 1. Assert: _stream_.[[state]] is `"writable"`. - 1. Let _controller_ be _stream_.[[writableStreamController]]. - 1. Assert: _controller_ is not *undefined*. - 1. Set _stream_.[[state]] to `"erroring"`. - 1. Set _stream_.[[storedError]] to _reason_. - 1. Let _writer_ be _stream_.[[writer]]. - 1. If _writer_ is not *undefined*, perform ! - WritableStreamDefaultWriterEnsureReadyPromiseRejected(_writer_, _reason_). - 1. If ! WritableStreamHasOperationMarkedInFlight(_stream_) is *false* and _controller_.[[started]] is *true*, perform - ! WritableStreamFinishErroring(_stream_). - - -

WritableStreamFinishErroring -( stream )

- - - 1. Assert: _stream_.[[state]] is `"erroring"`. - 1. Assert: ! WritableStreamHasOperationMarkedInFlight(_stream_) is *false*. - 1. Set _stream_.[[state]] to `"errored"`. - 1. Perform ! _stream_.[[writableStreamController]].[[ErrorSteps]](). - 1. Let _storedError_ be _stream_.[[storedError]]. - 1. Repeat for each _writeRequest_ that is an element of _stream_.[[writeRequests]], - 1. Reject _writeRequest_ with _storedError_. - 1. Set _stream_.[[writeRequests]] to an empty List. - 1. If _stream_.[[pendingAbortRequest]] is *undefined*, - 1. Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(_stream_). - 1. Return. - 1. Let _abortRequest_ be _stream_.[[pendingAbortRequest]]. - 1. Set _stream_.[[pendingAbortRequest]] to *undefined*. - 1. If _abortRequest_.[[wasAlreadyErroring]] is *true*, - 1. Reject _abortRequest_.[[promise]] with _storedError_. - 1. Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(_stream_). - 1. Return. - 1. Let _promise_ be ! stream.[[writableStreamController]].[[AbortSteps]](_abortRequest_.[[reason]]). - 1. Upon fulfillment of _promise_, - 1. Resolve _abortRequest_.[[promise]] with *undefined*. - 1. Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(_stream_). - 1. Upon rejection of _promise_ with reason _reason_, - 1. Reject _abortRequest_.[[promise]] with _reason_. - 1. Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(_stream_). - - -

WritableStreamFinishInFlightWrite ( stream )

- - - 1. Assert: _stream_.[[inFlightWriteRequest]] is not *undefined*. - 1. Resolve _stream_.[[inFlightWriteRequest]] with *undefined*. - 1. Set _stream_.[[inFlightWriteRequest]] to *undefined*. - - -

WritableStreamFinishInFlightWriteWithError ( stream, error )

- - - 1. Assert: _stream_.[[inFlightWriteRequest]] is not *undefined*. - 1. Reject _stream_.[[inFlightWriteRequest]] with _error_. - 1. Set _stream_.[[inFlightWriteRequest]] to *undefined*. - 1. Assert: _stream_.[[state]] is `"writable"` or `"erroring"`. - 1. Perform ! WritableStreamDealWithRejection(_stream_, _error_). - - -

WritableStreamFinishInFlightClose ( stream )

- - - 1. Assert: _stream_.[[inFlightCloseRequest]] is not *undefined*. - 1. Resolve _stream_.[[inFlightCloseRequest]] with *undefined*. - 1. Set _stream_.[[inFlightCloseRequest]] to *undefined*. - 1. Let _state_ be _stream_.[[state]]. - 1. Assert: _stream_.[[state]] is `"writable"` or `"erroring"`. - 1. If _state_ is `"erroring"`, - 1. Set _stream_.[[storedError]] to *undefined*. - 1. If _stream_.[[pendingAbortRequest]] is not *undefined*, - 1. Resolve _stream_.[[pendingAbortRequest]].[[promise]] with *undefined*. - 1. Set _stream_.[[pendingAbortRequest]] to *undefined*. - 1. Set _stream_.[[state]] to `"closed"`. - 1. Let _writer_ be _stream_.[[writer]]. - 1. If _writer_ is not *undefined*, resolve _writer_.[[closedPromise]] with *undefined*. - 1. Assert: _stream_.[[pendingAbortRequest]] is *undefined*. - 1. Assert: _stream_.[[storedError]] is *undefined*. - - -

WritableStreamFinishInFlightCloseWithError ( stream, error )

- - - 1. Assert: _stream_.[[inFlightCloseRequest]] is not *undefined*. - 1. Reject _stream_.[[inFlightCloseRequest]] with _error_. - 1. Set _stream_.[[inFlightCloseRequest]] to *undefined*. - 1. Assert: _stream_.[[state]] is `"writable"` or `"erroring"`. - 1. If _stream_.[[pendingAbortRequest]] is not *undefined*, - 1. Reject _stream_.[[pendingAbortRequest]].[[promise]] with _error_. - 1. Set _stream_.[[pendingAbortRequest]] to *undefined*. - 1. Perform ! WritableStreamDealWithRejection(_stream_, _error_). - - -

-WritableStreamCloseQueuedOrInFlight ( stream )

- - - 1. If _stream_.[[closeRequest]] is *undefined* and _stream_.[[inFlightCloseRequest]] is *undefined*, return *false*. - 1. Return *true*. - - -

WritableStreamHasOperationMarkedInFlight ( stream )

- - - 1. If _stream_.[[inFlightWriteRequest]] is *undefined* and _controller_.[[inFlightCloseRequest]] is *undefined*, return *false*. - 1. Return *true*. - - -

WritableStreamMarkCloseRequestInFlight ( stream )

- - - 1. Assert: _stream_.[[inFlightCloseRequest]] is *undefined*. - 1. Assert: _stream_.[[closeRequest]] is not *undefined*. - 1. Set _stream_.[[inFlightCloseRequest]] to _stream_.[[closeRequest]]. - 1. Set _stream_.[[closeRequest]] to *undefined*. - - -

WritableStreamMarkFirstWriteRequestInFlight ( stream )

- - - 1. Assert: _stream_.[[inFlightWriteRequest]] is *undefined*. - 1. Assert: _stream_.[[writeRequests]] is not empty. - 1. Let _writeRequest_ be the first element of _stream_.[[writeRequests]]. - 1. Remove _writeRequest_ from _stream_.[[writeRequests]], shifting all other elements downward (so that the second - becomes the first, and so on). - 1. Set _stream_.[[inFlightWriteRequest]] to _writeRequest_. - - -

WritableStreamRejectCloseAndClosedPromiseIfNeeded ( -stream )

- - - 1. Assert: _stream_.[[state]] is `"errored"`. - 1. If _stream_.[[closeRequest]] is not *undefined*, - 1. Assert: _stream_.[[inFlightCloseRequest]] is *undefined*. - 1. Reject _stream_.[[closeRequest]] with _stream_.[[storedError]]. - 1. Set _stream_.[[closeRequest]] to *undefined*. - 1. Let _writer_ be _stream_.[[writer]]. - 1. If _writer_ is not *undefined*, - 1. Reject _writer_.[[closedPromise]] with _stream_.[[storedError]]. - 1. Set _writer_.[[closedPromise]].[[PromiseIsHandled]] to *true*. - - -

WritableStreamUpdateBackpressure ( stream, backpressure )

- - - 1. Assert: _stream_.[[state]] is `"writable"`. - 1. Assert: ! WritableStreamCloseQueuedOrInFlight(_stream_) is *false*. - 1. Let _writer_ be _stream_.[[writer]]. - 1. If _writer_ is not *undefined* and _backpressure_ is not _stream_.[[backpressure]], - 1. If _backpressure_ is *true*, set _writer_.[[readyPromise]] to a new promise. - 1. Otherwise, - 1. Assert: _backpressure_ is *false*. - 1. Resolve _writer_.[[readyPromise]] with *undefined*. - 1. Set _stream_.[[backpressure]] to _backpressure_. - - -

Class -WritableStreamDefaultWriter

- -The {{WritableStreamDefaultWriter}} class represents a writable stream writer designed to be vended by a -{{WritableStream}} instance. - -

Class definition

+
+ ReadableByteStreamControllerClose(|controller|) + performs the following steps: + + 1. Let |stream| be |controller|.\[[controlledReadableStream]]. + 1. If |controller|.\[[closeRequested]] is true or |stream|.\[[state]] is not "`readable`", return. + 1. If |controller|.\[[queueTotalSize]] > 0, + 1. Set |controller|.\[[closeRequested]] to true. + 1. Return. + 1. If |controller|.\[[pendingPullIntos]] is not empty, + 1. Let |firstPendingPullInto| be |controller|.\[[pendingPullIntos]][0]. + 1. If |firstPendingPullInto|.\[[bytesFilled]] > 0, + 1. Let |e| be a new {{TypeError}} exception. + 1. Perform ! [$ReadableByteStreamControllerError$](|controller|, |e|). + 1. Throw |e|. + 1. Perform ! [$ReadableByteStreamControllerClearAlgorithms$](|controller|). + 1. Perform ! [$ReadableStreamClose$](|stream|). +
-
+
+ ReadableByteStreamControllerCommitPullIntoDescriptor(|stream|, + |pullIntoDescriptor|) performs the following steps: + + 1. Assert: |stream|.\[[state]] is not "`errored`". + 1. Let |done| be false. + 1. If |stream|.\[[state]] is "`closed`", + 1. Assert: |pullIntoDescriptor|.\[[bytesFilled]] is 0. + 1. Set |done| to true. + 1. Let |filledView| be ! + [$ReadableByteStreamControllerConvertPullIntoDescriptor$](|pullIntoDescriptor|). + 1. If |pullIntoDescriptor|.\[[readerType]] is "`default`", + 1. Perform ! [$ReadableStreamFulfillReadRequest$](|stream|, |filledView|, |done|). + 1. Otherwise, + 1. Assert: |pullIntoDescriptor|.\[[readerType]] is "`byob`". + 1. Perform ! [$ReadableStreamFulfillReadIntoRequest$](|stream|, |filledView|, |done|). +
-This section is non-normative. +
+ ReadableByteStreamControllerConvertPullIntoDescriptor(|pullIntoDescriptor|) + performs the following steps: + + 1. Let |bytesFilled| be |pullIntoDescriptor|.\[[bytesFilled]]. + 1. Let |elementSize| be |pullIntoDescriptor|.\[[elementSize]]. + 1. Assert: |bytesFilled| ≤ |pullIntoDescriptor|.\[[byteLength]]. + 1. Assert: |bytesFilled| mod |elementSize| is 0. + 1. Return ! [$Construct$](|pullIntoDescriptor|.\[[ctor]], « |pullIntoDescriptor|.\[[buffer]], + |pullIntoDescriptor|.\[[byteOffset]], |bytesFilled| ÷ |elementSize| »). +
-If one were to write the {{WritableStreamDefaultWriter}} class in something close to the syntax of [[!ECMASCRIPT]], it -would look like +
+ ReadableByteStreamControllerEnqueue(|controller|, + |chunk|) performs the following steps: + + 1. Let |stream| be |controller|.\[[controlledReadableStream]]. + 1. If |controller|.\[[closeRequested]] is true or |stream|.\[[state]] is not "`readable`", return. + 1. Let |buffer| be |chunk|.\[[ViewedArrayBuffer]]. + 1. Let |byteOffset| be |chunk|.\[[ByteOffset]]. + 1. Let |byteLength| be |chunk|.\[[ByteLength]]. + 1. Let |transferredBuffer| be ! [$TransferArrayBuffer$](|buffer|). + 1. If ! [$ReadableStreamHasDefaultReader$](|stream|) is true + 1. If ! [$ReadableStreamGetNumReadRequests$](|stream|) is 0, + 1. Perform ! [$ReadableByteStreamControllerEnqueueChunkToQueue$](|controller|, + |transferredBuffer|, |byteOffset|, |byteLength|). + 1. Otherwise, + 1. Assert: |controller|.\[[queue]] [=list/is empty=]. + 1. Let |transferredView| be ! [$Construct$]({{%Uint8Array%}}, « |transferredBuffer|, + |byteOffset|, |byteLength| »). + 1. Perform ! [$ReadableStreamFulfillReadRequest$](|stream|, |transferredView|, false). + 1. Otherwise, if ! [$ReadableStreamHasBYOBReader$](|stream|) is true, + 1. Perform ! [$ReadableByteStreamControllerEnqueueChunkToQueue$](|controller|, + |transferredBuffer|, |byteOffset|, |byteLength|). + 1. Perform ! [$ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue$](|controller|). + 1. Otherwise, + 1. Assert: ! [$IsReadableStreamLocked$](|stream|) is false. + 1. Perform ! [$ReadableByteStreamControllerEnqueueChunkToQueue$](|controller|, + |transferredBuffer|, |byteOffset|, |byteLength|). + 1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$](|controller|). +
-

-  class WritableStreamDefaultWriter {
-    constructor(stream)
+
+ ReadableByteStreamControllerEnqueueChunkToQueue(|controller|, + |buffer|, |byteOffset|, |byteLength|) performs the following steps: - get closed() - get desiredSize() - get ready() + 1. [=list/Append=] Record {\[[buffer]]: |buffer|, \[[byteOffset]]: |byteOffset|, \[[byteLength]]: + |byteLength|} to |controller|.\[[queue]]. + 1. Set |controller|.\[[queueTotalSize]] to |controller|.\[[queueTotalSize]] + |byteLength|. +
- abort(reason) - close() - releaseLock() - write(chunk) - } -
+
+ ReadableByteStreamControllerError(|controller|, + |e|) performs the following steps: + + 1. Let |stream| be |controller|.\[[controlledReadableStream]]. + 1. If |stream|.\[[state]] is not "`readable`", return. + 1. Perform ! [$ReadableByteStreamControllerClearPendingPullIntos$](|controller|). + 1. Perform ! [$ResetQueue$](|controller|). + 1. Perform ! [$ReadableByteStreamControllerClearAlgorithms$](|controller|). + 1. Perform ! [$ReadableStreamError$](|stream|, |e|). +
+ +
+ ReadableByteStreamControllerFillHeadPullIntoDescriptor(|controller|, + |size|, |pullIntoDescriptor|) performs the following steps: + 1. Assert: either |controller|.\[[pendingPullIntos]] [=list/is empty=], or + |controller|.\[[pendingPullIntos]][0] is |pullIntoDescriptor|. + 1. Perform ! [$ReadableByteStreamControllerInvalidateBYOBRequest$](|controller|). + 1. Set |pullIntoDescriptor|.\[[bytesFilled]] to |pullIntoDescriptor|.\[[bytesFilled]] + |size|.
-

Internal slots

+
+ ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(|controller|, + |pullIntoDescriptor|) performs the following steps: + + 1. Let |elementSize| be |pullIntoDescriptor|.\[[elementSize]]. + 1. Let |currentAlignedBytes| be |pullIntoDescriptor|.\[[bytesFilled]] − + (|pullIntoDescriptor|.\[[bytesFilled]] mod |elementSize|). + 1. Let |maxBytesToCopy| be min(|controller|.\[[queueTotalSize]], + |pullIntoDescriptor|.\[[byteLength]] − |pullIntoDescriptor|.\[[bytesFilled]]). + 1. Let |maxBytesFilled| be |pullIntoDescriptor|.\[[bytesFilled]] + |maxBytesToCopy|. + 1. Let |maxAlignedBytes| be |maxBytesFilled| − (|maxBytesFilled| mod |elementSize|). + 1. Let |totalBytesToCopyRemaining| be |maxBytesToCopy|. + 1. Let |ready| be false. + 1. If |maxAlignedBytes| > |currentAlignedBytes|, + 1. Set |totalBytesToCopyRemaining| to |maxAlignedBytes| − |pullIntoDescriptor|.\[[bytesFilled]]. + 1. Set |ready| to true. + 1. Let |queue| be |controller|.\[[queue]]. + 1. [=While=] |totalBytesToCopyRemaining| > 0, + 1. Let |headOfQueue| be |queue|[0]. + 1. Let |bytesToCopy| be min(|totalBytesToCopyRemaining|, |headOfQueue|.\[[byteLength]]). + 1. Let |destStart| be |pullIntoDescriptor|.\[[byteOffset]] + + |pullIntoDescriptor|.\[[bytesFilled]]. + 1. Perform ! [$CopyDataBlockBytes$](|pullIntoDescriptor|.\[[buffer]].\[[ArrayBufferData]], + |destStart|, |headOfQueue|.\[[buffer]].\[[ArrayBufferData]], |headOfQueue|.\[[byteOffset]], + |bytesToCopy|). + 1. If |headOfQueue|.\[[byteLength]] is |bytesToCopy|, + 1. [=list/Remove=] |queue|[0]. + 1. Otherwise, + 1. Set |headOfQueue|.\[[byteOffset]] to |headOfQueue|.\[[byteOffset]] + |bytesToCopy|. + 1. Set |headOfQueue|.\[[byteLength]] to |headOfQueue|.\[[byteLength]] − |bytesToCopy|. + 1. Set |controller|.\[[queueTotalSize]] to |controller|.\[[queueTotalSize]] − |bytesToCopy|. + 1. Perform ! [$ReadableByteStreamControllerFillHeadPullIntoDescriptor$](|controller|, + |bytesToCopy|, |pullIntoDescriptor|). + 1. Set |totalBytesToCopyRemaining| to |totalBytesToCopyRemaining| − |bytesToCopy|. + 1. If |ready| is false, + 1. Assert: |controller|.\[[queueTotalSize]] is 0. + 1. Assert: |pullIntoDescriptor|.\[[bytesFilled]] > 0. + 1. Assert: |pullIntoDescriptor|.\[[bytesFilled]] < |pullIntoDescriptor|.\[[elementSize]]. + 1. Return |ready|. +
+ +
+ ReadableByteStreamControllerGetDesiredSize(|controller|) + performs the following steps: + + 1. Let |stream| be |controller|.\[[controlledReadableStream]]. + 1. Let |state| be |stream|.\[[state]]. + 1. If |state| is "`errored`", return null. + 1. If |state| is "`closed`", return 0. + 1. Return |controller|.\[[strategyHWM]] − |controller|.\[[queueTotalSize]]. +
+ +
+ ReadableByteStreamControllerHandleQueueDrain(|controller|) + performs the following steps: + + 1. Assert: |controller|.\[[controlledReadableStream]].\[[state]] is "`readable`". + 1. If |controller|.\[[queueTotalSize]] is 0 and |controller|.\[[closeRequested]] is true, + 1. Perform ! [$ReadableByteStreamControllerClearAlgorithms$](|controller|). + 1. Perform ! [$ReadableStreamClose$](|controller|.\[[controlledReadableStream]]). + 1. Otherwise, + 1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$](|controller|). +
+ +
+ ReadableByteStreamControllerInvalidateBYOBRequest(|controller|) + performs the following steps: + + 1. If |controller|.\[[byobRequest]] is null, return. + 1. Set |controller|.\[[byobRequest]].\[[controller]] to undefined. + 1. Set |controller|.\[[byobRequest]].\[[view]] to null. + 1. Set |controller|.\[[byobRequest]] to null. +
+ +
+ ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(|controller|) + performs the following steps: + + 1. Assert: |controller|.\[[closeRequested]] is false. + 1. [=While=] |controller|.\[[pendingPullIntos]] is not [=list/is empty|empty=], + 1. If |controller|.\[[queueTotalSize]] is 0, return. + 1. Let |pullIntoDescriptor| be |controller|.\[[pendingPullIntos]][0]. + 1. If ! [$ReadableByteStreamControllerFillPullIntoDescriptorFromQueue$](|controller|, + |pullIntoDescriptor|) is true, + 1. Perform ! [$ReadableByteStreamControllerShiftPendingPullInto$](|controller|). + 1. Perform ! + [$ReadableByteStreamControllerCommitPullIntoDescriptor$](|controller|.\[[controlledReadableStream]], + |pullIntoDescriptor|). +
+ +
+ ReadableByteStreamControllerPullInto(|controller|, + |view|) performs the following steps: + + 1. Let |stream| be |controller|.\[[controlledReadableStream]]. + 1. Let |elementSize| be 1. + 1. Let |ctor| be {{%DataView%}}. + 1. If |view| has a \[[TypedArrayName]] internal slot (i.e., it is not a {{DataView}}), + 1. Set |elementSize| to the element size specified in [=the typed array constructors table=] for + |view|.\[[TypedArrayName]]. + 1. Set |ctor| to the constructor specified in [=the typed array constructors table=] for + |view|.\[[TypedArrayName]]. + 1. Let |byteOffset| be |view|.\[[ByteOffset]]. + 1. Let |byteLength| be |view|.\[[ByteLength]]. + 1. Let |buffer| be ! [$TransferArrayBuffer$](|view|.\[[ViewedArrayBuffer]]). + 1. Let |pullIntoDescriptor| be Record {\[[buffer]]: |buffer|, \[[byteOffset]]: |byteOffset|, + \[[byteLength]]: |byteLength|, \[[bytesFilled]]: 0, \[[elementSize]]: |elementSize|, + \[[ctor]]: |ctor|, \[[readerType]]: "`byob`"}. + 1. If |controller|.\[[pendingPullIntos]] is not empty, + 1. [=list/Append=] |pullIntoDescriptor| to |controller|.\[[pendingPullIntos]]. + 1. Return ! [$ReadableStreamAddReadIntoRequest$](|stream|). + 1. If |stream|.\[[state]] is "`closed`", + 1. Let |emptyView| be ! [$Construct$](|ctor|, « |pullIntoDescriptor|.\[[buffer]], + |pullIntoDescriptor|.\[[byteOffset]], 0 »). + 1. Return [=a promise resolved with=] ! [$ReadableStreamCreateReadResult$](|emptyView|, true, + |stream|.\[[reader]].\[[forAuthorCode]]). + 1. If |controller|.\[[queueTotalSize]] > 0, + 1. If ! [$ReadableByteStreamControllerFillPullIntoDescriptorFromQueue$](|controller|, + |pullIntoDescriptor|) is true, + 1. Let |filledView| be ! + [$ReadableByteStreamControllerConvertPullIntoDescriptor$](|pullIntoDescriptor|). + 1. Perform ! [$ReadableByteStreamControllerHandleQueueDrain$](|controller|). + 1. Return [=a promise resolved with=] ! [$ReadableStreamCreateReadResult$](|filledView|, false, + |stream|.\[[reader]].\[[forAuthorCode]]). + 1. If |controller|.\[[closeRequested]] is true, + 1. Let |e| be a {{TypeError}} exception. + 1. Perform ! [$ReadableByteStreamControllerError$](|controller|, |e|). + 1. Return [=a promise rejected with=] |e|. + 1. [=list/Append=] |pullIntoDescriptor| to |controller|.\[[pendingPullIntos]]. + 1. Let |promise| be ! [$ReadableStreamAddReadIntoRequest$](|stream|). + 1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$](|controller|). + 1. Return |promise|. +
+ +
+ ReadableByteStreamControllerRespond(|controller|, + |bytesWritten|) performs the following steps: + + 1. Assert: |controller|.\[[pendingPullIntos]] is not empty. + 1. Perform ? [$ReadableByteStreamControllerRespondInternal$](|controller|, |bytesWritten|). +
+ +
+ ReadableByteStreamControllerRespondInClosedState(|controller|, + |firstDescriptor|) performs the following steps: + + 1. Set |firstDescriptor|.\[[buffer]] to ! [$TransferArrayBuffer$](|firstDescriptor|.\[[buffer]]). + 1. Assert: |firstDescriptor|.\[[bytesFilled]] is 0. + 1. Let |stream| be |controller|.\[[controlledReadableStream]]. + 1. If ! [$ReadableStreamHasBYOBReader$](|stream|) is true, + 1. [=While=] ! [$ReadableStreamGetNumReadIntoRequests$](|stream|) > 0, + 1. Let |pullIntoDescriptor| be ! + [$ReadableByteStreamControllerShiftPendingPullInto$](|controller|). + 1. Perform ! [$ReadableByteStreamControllerCommitPullIntoDescriptor$](|stream|, + |pullIntoDescriptor|). +
+ +
+ ReadableByteStreamControllerRespondInReadableState(|controller|, + |bytesWritten|, |pullIntoDescriptor|) performs the following steps: + + 1. If |pullIntoDescriptor|.\[[bytesFilled]] + |bytesWritten| > + |pullIntoDescriptor|.\[[byteLength]], throw a {{RangeError}} exception. + 1. Perform ! [$ReadableByteStreamControllerFillHeadPullIntoDescriptor$](|controller|, + |bytesWritten|, |pullIntoDescriptor|). + 1. If |pullIntoDescriptor|.\[[bytesFilled]] < |pullIntoDescriptor|.\[[elementSize]], return. + 1. Perform ! [$ReadableByteStreamControllerShiftPendingPullInto$](|controller|). + 1. Let |remainderSize| be |pullIntoDescriptor|.\[[bytesFilled]] mod + |pullIntoDescriptor|.\[[elementSize]]. + 1. If |remainderSize| > 0, + 1. Let |end| be |pullIntoDescriptor|.\[[byteOffset]] + |pullIntoDescriptor|.\[[bytesFilled]]. + 1. Let |remainder| be ? [$CloneArrayBuffer$](|pullIntoDescriptor|.\[[buffer]], |end| − + |remainderSize|, |remainderSize|, {{%ArrayBuffer%}}). + 1. Perform ! [$ReadableByteStreamControllerEnqueueChunkToQueue$](|controller|, |remainder|, 0, + |remainder|.\[[ByteLength]]). + 1. Set |pullIntoDescriptor|.\[[buffer]] to ! [$TransferArrayBuffer$](|pullIntoDescriptor|.\[[buffer]]). + 1. Set |pullIntoDescriptor|.\[[bytesFilled]] to |pullIntoDescriptor|.\[[bytesFilled]] − |remainderSize|. + 1. Perform ! + [$ReadableByteStreamControllerCommitPullIntoDescriptor$](|controller|.\[[controlledReadableStream]], + |pullIntoDescriptor|). + 1. Perform ! [$ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue$](|controller|). +
+ +
+ ReadableByteStreamControllerRespondInternal(|controller|, + |bytesWritten|) performs the following steps: + + 1. Let |firstDescriptor| be |controller|.\[[pendingPullIntos]][0]. + 1. Let |stream| be |controller|.\[[controlledReadableStream]]. + 1. If |stream|.\[[state]] is "`closed`", + 1. If |bytesWritten| is not 0, throw a {{TypeError}} exception. + 1. Perform ! [$ReadableByteStreamControllerRespondInClosedState$](|controller|, + |firstDescriptor|). + 1. Otherwise, + 1. Assert: |stream|.\[[state]] is "`readable`". + 1. Perform ? [$ReadableByteStreamControllerRespondInReadableState$](|controller|, |bytesWritten|, + |firstDescriptor|). + 1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$](|controller|). +
+ +
+ ReadableByteStreamControllerRespondWithNewView(|controller|, + |view|) performs the following steps: + + 1. Assert: |controller|.\[[pendingPullIntos]] is not [=list/is empty|empty=]. + 1. Let |firstDescriptor| be |controller|.\[[pendingPullIntos]][0]. + 1. If |firstDescriptor|.\[[byteOffset]] + |firstDescriptor|.\[[bytesFilled]] is not + |view|.\[[ByteOffset]], throw a {{RangeError}} exception. + 1. If |firstDescriptor|.\[[byteLength]] is not |view|.\[[ByteLength]], throw a {{RangeError}} + exception. + 1. Set |firstDescriptor|.\[[buffer]] to |view|.\[[ViewedArrayBuffer]]. + 1. Perform ? [$ReadableByteStreamControllerRespondInternal$](|controller|, |view|.\[[ByteLength]]). +
+ +
+ ReadableByteStreamControllerShiftPendingPullInto(|controller|) + performs the following steps: + + 1. Let |descriptor| be |controller|.\[[pendingPullIntos]][0]. + 1. [=list/Remove=] |descriptor| from |controller|.\[[pendingPullIntos]]. + 1. Perform ! [$ReadableByteStreamControllerInvalidateBYOBRequest$](|controller|). + 1. Return |descriptor|. +
+ +
+ ReadableByteStreamControllerShouldCallPull(|controller|) + performs the following steps: + + 1. Let |stream| be |controller|.\[[controlledReadableStream]]. + 1. If |stream|.\[[state]] is not "`readable`", return false. + 1. If |controller|.\[[closeRequested]] is true, return false. + 1. If |controller|.\[[started]] is false, return false. + 1. If ! [$ReadableStreamHasDefaultReader$](|stream|) is true and ! + [$ReadableStreamGetNumReadRequests$](|stream|) > 0, return true. + 1. If ! [$ReadableStreamHasBYOBReader$](|stream|) is true and ! + [$ReadableStreamGetNumReadIntoRequests$](|stream|) > 0, return true. + 1. Let |desiredSize| be ! [$ReadableByteStreamControllerGetDesiredSize$](|controller|). + 1. Assert: |desiredSize| is not null. + 1. If |desiredSize| > 0, return true. + 1. Return false. +
+ +
+ SetUpReadableByteStreamController(|stream|, + |controller|, |startAlgorithm|, |pullAlgorithm|, |cancelAlgorithm|, |highWaterMark|, + |autoAllocateChunkSize|) performs the following steps: + + 1. Assert: |stream|.\[[readableStreamController]] is undefined. + 1. If |autoAllocateChunkSize| is not undefined, + 1. Assert: ! IsInteger(|autoAllocateChunkSize|) is true. + 1. Assert: |autoAllocateChunkSize| is positive. + 1. Set |controller|.\[[controlledReadableStream]] to |stream|. + 1. Set |controller|.\[[pullAgain]] and |controller|.\[[pulling]] to false. + 1. Set |controller|.\[[byobRequest]] to null. + 1. Perform ! [$ResetQueue$](|controller|). + 1. Set |controller|.\[[closeRequested]] and |controller|.\[[started]] to false. + 1. Set |controller|.\[[strategyHWM]] to |highWaterMark|. + 1. Set |controller|.\[[pullAlgorithm]] to |pullAlgorithm|. + 1. Set |controller|.\[[cancelAlgorithm]] to |cancelAlgorithm|. + 1. Set |controller|.\[[autoAllocateChunkSize]] to |autoAllocateChunkSize|. + 1. Set |controller|.\[[pendingPullIntos]] to a new empty [=list=]. + 1. Set |stream|.\[[readableStreamController]] to |controller|. + 1. Let |startResult| be the result of performing |startAlgorithm|. + 1. Let |startPromise| be [=a promise resolved with=] |startResult|. + 1. [=Upon fulfillment=] of |startPromise|, + 1. Set |controller|.\[[started]] to true. + 1. Assert: |controller|.\[[pulling]] is false. + 1. Assert: |controller|.\[[pullAgain]] is false. + 1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$](|controller|). + 1. [=Upon rejection=] of |startPromise| with reason |r|, + 1. Perform ! [$ReadableByteStreamControllerError$](|controller|, |r|). +
+ +
+ SetUpReadableByteStreamControllerFromUnderlyingSource(|stream|, + |underlyingSource|, |underlyingSourceDict|, |highWaterMark|) performs the following steps: + + 1. Let |controller| be a [=new=] {{ReadableByteStreamController}}. + 1. Let |startAlgorithm| be an algorithm that returns undefined. + 1. Let |pullAlgorithm| be an algorithm that returns [=a promise resolved with=] undefined. + 1. Let |cancelAlgorithm| be an algorithm that returns [=a promise resolved with=] undefined. + 1. If |underlyingSourceDict|["{{UnderlyingSource/start}}"] [=map/exists=], then set + |startAlgorithm| to an algorithm which returns the result of [=invoking=] + |underlyingSourceDict|["{{UnderlyingSource/start}}"] with argument list + « |controller| » and [=callback this value=] |underlyingSource|. + 1. If |underlyingSourceDict|["{{UnderlyingSource/pull}}"] [=map/exists=], then set + |pullAlgorithm| to an algorithm which returns the result of [=invoking=] + |underlyingSourceDict|["{{UnderlyingSource/pull}}"] with argument list + « |controller| » and [=callback this value=] |underlyingSource|. + 1. If |underlyingSourceDict|["{{UnderlyingSource/cancel}}"] [=map/exists=], then set + |cancelAlgorithm| to an algorithm which takes an argument |reason| and returns the result of + [=invoking=] |underlyingSourceDict|["{{UnderlyingSource/cancel}}"] with argument list + « |reason| » and [=callback this value=] |underlyingSource|. + 1. Let |autoAllocateChunkSize| be + |underlyingSourceDict|["{{UnderlyingSource/autoAllocateChunkSize}}"], if it [=map/exists=], or + undefined otherwise. + 1. Perform ? [$SetUpReadableByteStreamController$](|stream|, |controller|, |startAlgorithm|, + |pullAlgorithm|, |cancelAlgorithm|, |highWaterMark|, |autoAllocateChunkSize|). +
+

Writable streams

+ +

Using writable streams

+ +
+ The usual way to write to a writable stream is to simply [=piping|pipe=] a [=readable stream=] to + it. This ensures that [=backpressure=] is respected, so that if the writable stream's [=underlying + sink=] is not able to accept data as fast as the readable stream can produce it, the readable + stream is informed of this and has a chance to slow down its data production. + + + readableStream.pipeTo(writableStream) + .then(() => console.log("All data successfully written!")) + .catch(e => console.error("Something went wrong!", e)); + +
+ +
+ You can also write directly to writable streams by acquiring a [=writer=] and using its + {{WritableStreamDefaultWriter/write()}} and {{WritableStreamDefaultWriter/close()}} methods. Since + writable streams queue any incoming writes, and take care internally to forward them to the + [=underlying sink=] in sequence, you can indiscriminately write to a writable stream without much + ceremony: + + + function writeArrayToStream(array, writableStream) { + const writer = writableStream.getWriter(); + array.forEach(chunk => writer.write(chunk).catch(() => {})); + + return writer.close(); + } + + writeArrayToStream([1, 2, 3, 4, 5], writableStream) + .then(() => console.log("All done!")) + .catch(e => console.error("Error with the stream: " + e)); + + + Note how we use .catch(() => {}) to suppress any rejections from the + {{WritableStreamDefaultWriter/write()}} method; we'll be notified of any fatal errors via a + rejection of the {{WritableStreamDefaultWriter/close()}} method, and leaving them un-caught would + cause potential {{unhandledrejection}} events and console warnings. +
+ +
+ In the previous example we only paid attention to the success or failure of the entire stream, by + looking at the promise returned by the writer's {{WritableStreamDefaultWriter/close()}} method. + That promise will reject if anything goes wrong with the stream—initializing it, writing to it, or + closing it. And it will fulfill once the stream is successfully closed. Often this is all you care + about. + + However, if you care about the success of writing a specific [=chunk=], you can use the promise + returned by the writer's {{WritableStreamDefaultWriter/write()}} method: + + + writer.write("i am a chunk of data") + .then(() => console.log("chunk successfully written!")) + .catch(e => console.error(e)); + + + What "success" means is up to a given stream instance (or more precisely, its [=underlying sink=]) + to decide. For example, for a file stream it could simply mean that the OS has accepted the write, + and not necessarily that the chunk has been flushed to disk. Some streams might not be able to + give such a signal at all, in which case the returned promise will fulfill immediately. +
+ +
+ The {{WritableStreamDefaultWriter/desiredSize}} and {{WritableStreamDefaultWriter/ready}} + properties of writable stream writers allow [=producers=] to more precisely respond to flow + control signals from the stream, to keep memory usage below the stream's specified [=high water + mark=]. The following example writes an infinite sequence of random bytes to a stream, using + {{WritableStreamDefaultWriter/desiredSize}} to determine how many bytes to generate at a given + time, and using {{WritableStreamDefaultWriter/ready}} to wait for the [=backpressure=] to subside. + + + async function writeRandomBytesForever(writableStream) { + const writer = writableStream.getWriter(); + + while (true) { + await writer.ready; + + const bytes = new Uint8Array(writer.desiredSize); + crypto.getRandomValues(bytes); + + // Purposefully don't await; awaiting writer.ready is enough. + writer.write(bytes).catch(() => {}); + } + } + + writeRandomBytesForever(myWritableStream).catch(e => console.error("Something broke", e)); + + + Note how we don't await the promise returned by + {{WritableStreamDefaultWriter/write()}}; this would be redundant with awaiting the + {{WritableStreamDefaultWriter/ready}} promise. Additionally, similar to a previous example, we use the .catch(() => + {}) pattern on the promises returned by {{WritableStreamDefaultWriter/write()}}; in this + case we'll be notified about any failures + awaiting the {{WritableStreamDefaultWriter/ready}} promise. +
+ +
+ To further emphasize how it's a bad idea to await the promise returned by + {{WritableStreamDefaultWriter/write()}}, consider a modification of the above example, where we + continue to use the {{WritableStreamDefaultWriter}} interface directly, but we don't control how + many bytes we have to write at a given time. In that case, the [=backpressure=]-respecting code + looks the same: + + + async function writeSuppliedBytesForever(writableStream, getBytes) { + const writer = writableStream.getWriter(); + + while (true) { + await writer.ready; + + const bytes = getBytes(); + writer.write(bytes).catch(() => {}); + } + } + + + Unlike the previous example, where—because we were always writing exactly + {{WritableStreamDefaultWriter/desiredSize|writer.desiredSize}} bytes each time—the + {{WritableStreamDefaultWriter/write()}} and {{WritableStreamDefaultWriter/ready}} promises were + synchronized, in this case it's quite possible that the {{WritableStreamDefaultWriter/ready}} + promise fulfills before the one returned by {{WritableStreamDefaultWriter/write()}} does. + Remember, the {{WritableStreamDefaultWriter/ready}} promise fulfills when the [=desired size to + fill a stream's internal queue|desired size=] becomes positive, which might be before the write + succeeds (especially in cases with a larger [=high water mark=]). + + In other words, awaiting the return value of {{WritableStreamDefaultWriter/write()}} + means you never queue up writes in the stream's [=internal queue=], instead only executing a write + after the previous one succeeds, which can result in low throughput. +
+ +

The {{WritableStream}} class

-Instances of {{WritableStreamDefaultWriter}} are created with the internal slots described in the following table: +The {{WritableStream}} represents a [=writable stream=]. + +

Interface definition

+ +The Web IDL definition for the {{WritableStream}} class is given as follows: + + +[Exposed=(Window,Worker,Worklet)] +interface WritableStream { + constructor(optional object underlyingSink, optional QueuingStrategy strategy = {}); + + readonly attribute boolean locked; + + Promise<void> abort(optional any reason); + Promise<void> close(); + WritableStreamDefaultWriter getWriter(); +}; + + +

Internal slots

+ +Instances of {{WritableStream}} are created with the internal slots described in the following +table: - - - - - - - - + - + - + + + + + + + + + +
Internal SlotDescription (non-normative)
\[[closedPromise]] - A promise returned by the writer's {{WritableStreamDefaultWriter/closed}} getter -
\[[ownerWritableStream]] - A {{WritableStream}} instance that owns this writer -
Internal Slot + Description (non-normative) +
\[[readyPromise]] - A promise returned by the writer's {{WritableStreamDefaultWriter/ready}} getter -
\[[backpressure]] + A boolean indicating the backpressure signal set by the controller +
\[[closeRequest]] + The promise returned from the writer's + {{WritableStreamDefaultWriter/close()}} method +
\[[inFlightWriteRequest]] + A slot set to the promise for the current in-flight write operation + while the [=underlying sink=]'s write algorithm is executing and has not yet fulfilled, used to + prevent reentrant calls +
\[[inFlightCloseRequest]] + A slot set to the promise for the current in-flight close operation + while the [=underlying sink=]'s close algorithm is executing and has not yet fulfilled, used to + prevent the {{WritableStreamDefaultWriter/abort()}} method from interrupting close +
\[[pendingAbortRequest]] + A Record containing the promise returned from + {{WritableStreamDefaultWriter/abort()}} and the reason passed to + {{WritableStreamDefaultWriter/abort()}} +
\[[state]] + A string containing the stream's current state, used internally; one of + "`writable`", "`closed`", "`erroring`", or "`errored`" +
\[[storedError]] + A value indicating how the stream failed, to be given as a failure + reason or exception when trying to operate on the stream while in the "`errored`" state +
\[[writableStreamController]] + A {{WritableStreamDefaultController}} created with the ability to + control the state and queue of this stream +
\[[writer]] + A {{WritableStreamDefaultWriter}} instance, if the stream is [=locked to + a writer=], or undefined if it is not +
\[[writeRequests]] + A [=list=] of promises representing the stream's internal queue of write + requests not yet processed by the [=underlying sink=]
-

new WritableStreamDefaultWriter(stream)

+

The \[[inFlightCloseRequest]] slot and \[[closeRequest]] slot are mutually +exclusive. Similarly, no element will be removed from \[[writeRequests]] while +\[[inFlightWriteRequest]] is not undefined. Implementations can optimize storage for these slots +based on these invariants. -

- The WritableStreamDefaultWriter constructor is generally not meant to be used directly; instead, a - stream's {{WritableStream/getWriter()}} method ought to be used. -
- - - 1. If ! IsWritableStream(_stream_) is *false*, throw a *TypeError* exception. - 1. If ! IsWritableStreamLocked(_stream_) is *true*, throw a *TypeError* exception. - 1. Set *this*.[[ownerWritableStream]] to _stream_. - 1. Set _stream_.[[writer]] to *this*. - 1. Let _state_ be _stream_.[[state]]. - 1. If _state_ is `"writable"`, - 1. If ! WritableStreamCloseQueuedOrInFlight(_stream_) is *false* and _stream_.[[backpressure]] is *true*, - set *this*.[[readyPromise]] to a new promise. - 1. Otherwise, set *this*.[[readyPromise]] to a promise resolved with *undefined*. - 1. Set *this*.[[closedPromise]] to a new promise. - 1. Otherwise, if _state_ is `"erroring"`, - 1. Set *this*.[[readyPromise]] to a promise rejected with _stream_.[[storedError]]. - 1. Set *this*.[[readyPromise]].[[PromiseIsHandled]] to *true*. - 1. Set *this*.[[closedPromise]] to a new promise. - 1. Otherwise, if _state_ is `"closed"`, - 1. Set *this*.[[readyPromise]] to a promise resolved with *undefined*. - 1. Set *this*.[[closedPromise]] to a promise resolved with *undefined*. - 1. Otherwise, - 1. Assert: _state_ is `"errored"`. - 1. Let _storedError_ be _stream_.[[storedError]]. - 1. Set *this*.[[readyPromise]] to a promise rejected with _storedError_. - 1. Set *this*.[[readyPromise]].[[PromiseIsHandled]] to *true*. - 1. Set *this*.[[closedPromise]] to a promise rejected with _storedError_. - 1. Set *this*.[[closedPromise]].[[PromiseIsHandled]] to *true*. - +

The underlying sink API

-

Properties of the {{WritableStreamDefaultWriter}} prototype

+The {{WritableStream()}} constructor accepts as its first argument a JavaScript object representing +the [=underlying sink=]. Such objects can contain any of the following properties: -
get closed
+ +dictionary UnderlyingSink { + UnderlyingSinkStartCallback start; + UnderlyingSinkWriteCallback write; + UnderlyingSinkCloseCallback close; + UnderlyingSinkAbortCallback abort; + any type; +}; -<div class="note"> - The <code>closed</code> getter returns a promise that will be fulfilled when the stream becomes closed, or rejected if - the stream ever errors or the writer's lock is <a lt="release a write lock">released</a> before the stream finishes - closing. -</div> +callback UnderlyingSinkStartCallback = any (WritableStreamDefaultController controller); +callback UnderlyingSinkWriteCallback = Promise<void> (WritableStreamDefaultController controller, optional any chunk); +callback UnderlyingSinkCloseCallback = Promise<void> (); +callback UnderlyingSinkAbortCallback = Promise<void> (optional any reason); + - - 1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, return a promise rejected with a *TypeError* exception. - 1. Return *this*.[[closedPromise]]. - +
+
start(controller)
+
+

A function that is called immediately during creation of the {{WritableStream}}. -

get desiredSize
+

Typically this is used to acquire access to the [=underlying sink=] resource being + represented. -

- The desiredSize getter returns the desired size to - fill the stream's internal queue. It can be negative, if the queue is over-full. A producer can use this - information to determine the right amount of data to write. +

If this setup process is asynchronous, it can return a promise to signal success or failure; a + rejected promise will error the stream. Any thrown exceptions will be re-thrown by the + {{WritableStream()}} constructor. - It will be null if the stream cannot be successfully written to (due to either being errored, or - having an abort queued up). It will return zero if the stream is closed. The getter will throw an exception if invoked - when the writer's lock is released. -

+
write(chunk, + controller)
+
+

A function that is called when a new [=chunk=] of data is ready to be written to the + [=underlying sink=]. The stream implementation guarantees that this function will be called only + after previous writes have succeeded, and never before {{UnderlyingSink/start|start()}} has + succeeded or after {{UnderlyingSink/close|close()}} or {{UnderlyingSink/abort|abort()}} have + been called. + +

This function is used to actually send the data to the resource presented by the [=underlying + sink=], for example by calling a lower-level API. + +

If the process of writing data is asynchronous, and communicates success or failure signals + back to its user, then this function can return a promise to signal success or failure. This + promise return value will be communicated back to the caller of + {{WritableStreamDefaultWriter/write()|writer.write()}}, so they can monitor that individual + write. Throwing an exception is treated the same as returning a rejected promise. + +

Note that such signals are not always available; compare e.g. [[#example-ws-no-backpressure]] + with [[#example-ws-backpressure]]. In such cases, it's best to not return anything. + +

The promise potentially returned by this function also governs whether the given chunk counts + as written for the purposes of computed the [=desired size to fill a stream's internal + queue|desired size to fill the stream's internal queue=]. That is, during the time it takes the + promise to settle, {{WritableStreamDefaultWriter/desiredSize|writer.desiredSize}} will stay at + its previous value, only increasing to signal the desire for more chunks once the write + succeeds. + +

close()
+
+

A function that is called after the [=producer=] signals, via + {{WritableStreamDefaultWriter/close()|writer.close()}}, that they are done writing [=chunks=] to + the stream, and subsequently all queued-up writes have successfully completed. - - 1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, throw a *TypeError* exception. - 1. If *this*.[[ownerWritableStream]] is *undefined*, throw a *TypeError* exception. - 1. Return ! WritableStreamDefaultWriterGetDesiredSize(*this*). - +

This function can perform any actions necessary to finalize or flush writes to the + [=underlying sink=], and release access to any held resources. -

get ready
+

If the shutdown process is asynchronous, the function can return a promise to signal success + or failure; the result will be communicated via the return value of the called + {{WritableStreamDefaultWriter/close()|writer.close()}} method. Additionally, a rejected promise + will error the stream, instead of letting it close successfully. Throwing an exception is + treated the same as returning a rejected promise. -

- The ready getter returns a promise that will be fulfilled when the desired size to fill the stream's internal queue transitions from non-positive to positive, - signaling that it is no longer applying backpressure. Once the desired size to fill the stream's internal queue dips back to zero or below, the getter will return a new - promise that stays pending until the next transition. +
abort(reason)
+
+

A function that is called after the [=producer=] signals, via + {{WritableStream/abort()|stream.abort()}} or + {{WritableStreamDefaultWriter/abort()|writer.abort()}}, that they wish to [=abort a writable + stream|abort=] the stream. It takes as its argument the same value as was passed to those + methods by the producer. + +

Writable streams can additionally be aborted under certain conditions during [=piping=]; see + the definition of the {{ReadableStream/pipeTo()}} method for more details. - If the stream becomes errored or aborted, or the writer's lock is released, the - returned promise will become rejected. +

This function can clean up any held resources, much like {{UnderlyingSink/close|close()}}, + but perhaps with some custom handling. + +

If the shutdown process is asynchronous, the function can return a promise to signal success + or failure; the result will be communicated via the return value of the called + {{WritableStreamDefaultWriter/abort()|writer.abort()}} method. Throwing an exception is treated + the same as returning a rejected promise. Regardless, the stream will be errored with a new + {{TypeError}} indicating that it was aborted. + +

type
+
+

This property is reserved for future use, so any attempts to supply a value will throw an + exception. +

+ +The controller argument passed to {{UnderlyingSink/start|start()}} and +{{UnderlyingSink/write|write()}} is an instance of {{WritableStreamDefaultController}}, and has the +ability to error the stream. This is mainly used for bridging the gap with non-promise-based APIs, +as seen for example in [[#example-ws-no-backpressure]]. + +

Constructor, methods, and properties

+ +
+
stream = new {{WritableStream/constructor(underlyingSink, strategy)|WritableStream}}(underlyingSink[, strategy) +
+

Creates a new {{WritableStream}} wrapping the provided [=underlying sink=]. See + [[#underlying-sink-api]] for more details on the underlyingSink argument. + +

The |strategy| argument represents the stream's [=queuing strategy=], as described in + [[#qs-api]]. If it is not provided, the default behavior will be the same as a + {{CountQueuingStrategy}} with a [=high water mark=] of 1. + +

isLocked = stream.{{WritableStream/locked}} +
+

Returns whether or not the writable stream is [=locked to a writer=]. + +

await stream.{{WritableStream/abort(reason)|abort}}([ reason ]) +
+

[=abort a writable stream|Aborts=] the stream, signaling that the producer can no longer + successfully write to the stream and it is to be immediately moved to an errored state, with any + queued-up writes discarded. This will also execute any abort mechanism of the [=underlying + sink=]. + +

The returned promise will fulfill if the stream shuts down successfully, or reject if the + underlying sink signaled that there was an error doing so. Additionally, it will reject with a + {{TypeError}} (without attempting to cancel the stream) if the stream is currently [=locked to a + writer|locked=]. + +

await stream.{{WritableStream/close()|close}}() +
+

Closes the stream. The [=underlying sink=] will finish processing any previously-written + [=chunks=], before invoking its close behavior. During this time any further attempts to write + will fail (without erroring the stream). + +

The method returns a promise that will fulfill if all remaining [=chunks=] are successfully + written and the stream successfully closes, or rejects if an error is encountered during this + process. Additionally, it will reject with a {{TypeError}} (without attempting to cancel the + stream) if the stream is currently [=locked to a writer|locked=]. + +

writer = stream.{{WritableStream/getWriter()|getWriter}}() +
+

Creates a [=writer=] (an instance of {{WritableStreamDefaultWriter}}) and [=locked to a + writer|locks=] the stream to the new writer. While the stream is locked, no other writer can be + acquired until this one is [=release a write lock|released=]. + +

This functionality is especially useful for creating abstractions that desire the ability to + write to a stream without interruption or interleaving. By getting a writer for the stream, you + can ensure nobody else can write at the same time, which would cause the resulting written data + to be unpredictable and probably useless. +

+ +
+ The WritableStream(|underlyingSink|, + |strategy|) constructor steps are: + + 1. If |underlyingSink| is missing, set it to null. + 1. Let |underlyingSinkDict| be |underlyingSink|, [=converted to an IDL value=] of type + {{UnderlyingSink}}. +

We cannot declare the |underlyingSink| argument as having the {{UnderlyingSink}} + type directly, because doing so would lose the reference to the original object. We need to + retain the object so we can [=invoke=] the various methods on it. + 1. If |underlyingSinkDict|["{{UnderlyingSink/type}}"] [=map/exists=], throw a {{RangeError}} + exception. +

This is to allow us to add new potential types in the future, without + backward-compatibility concerns. + 1. Perform ! [$InitializeWritableStream$]([=this=]). + 1. Let |sizeAlgorithm| be ! [$ExtractSizeAlgorithm$](|strategy|). + 1. Let |highWaterMark| be ? [$ExtractHighWaterMark$](|strategy|, 1). + 1. Perform ? [$SetUpWritableStreamDefaultControllerFromUnderlyingSink$]([=this=], |underlyingSink|, + |underlyingSinkDict|, |highWaterMark|, |sizeAlgorithm|).

- - 1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, return a promise rejected with a *TypeError* exception. - 1. Return *this*.[[readyPromise]]. - +
+ The locked attribute's getter steps are: -
abort(reason)
+ 1. Return ! [$IsWritableStreamLocked$]([=this=]). +
-
- If the writer is active, the abort method behaves the same as that for the - associated stream. (Otherwise, it returns a rejected promise.) +
+ The abort(|reason|) method steps are: + + 1. If ! [$IsWritableStreamLocked$]([=this=]) is true, return [=a promise rejected with=] a + {{TypeError}} exception. + 1. Return ! [$WritableStreamAbort$]([=this=], |reason|).
- - 1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, return a promise rejected with a *TypeError* exception. - 1. If *this*.[[ownerWritableStream]] is *undefined*, return a promise rejected with a *TypeError* exception. - 1. Return ! WritableStreamDefaultWriterAbort(*this*, _reason_). - +
+ The close() method steps are: -
close()
+ 1. If ! [$IsWritableStreamLocked$]([=this=]) is true, return [=a promise rejected with=] a + {{TypeError}} exception. + 1. If ! [$WritableStreamCloseQueuedOrInFlight$]([=this=]) is true, return [=a promise rejected + with=] a {{TypeError}} exception. + 1. Return ! [$WritableStreamClose$]([=this=]). +
-
- If the writer is active, the close method behaves the same as that for the - associated stream. (Otherwise, it returns a rejected promise.) +
+ The getWriter() method steps are: + + 1. Return ? [$AcquireWritableStreamDefaultWriter$]([=this=]).
- - 1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, return a promise rejected with a *TypeError* exception. - 1. Let _stream_ be *this*.[[ownerWritableStream]]. - 1. If _stream_ is *undefined*, return a promise rejected with a *TypeError* exception. - 1. If ! WritableStreamCloseQueuedOrInFlight(_stream_) is *true*, return a promise rejected with a *TypeError* - exception. - 1. Return ! WritableStreamDefaultWriterClose(*this*). - +

The {{WritableStreamDefaultWriter}} class

-
releaseLock()
+The {{WritableStreamDefaultWriter}} class represents a [=writable stream writer=] designed to be +vended by a {{WritableStream}} instance. -
- The releaseLock method releases the writer's lock on the corresponding - stream. After the lock is released, the writer is no longer active. If the associated - stream is errored when the lock is released, the writer will appear errored in the same way from now on; otherwise, +

Interface definition

+ +The Web IDL definition for the {{WritableStreamDefaultWriter}} class is given as follows: + + +[Exposed=(Window,Worker,Worklet)] +interface WritableStreamDefaultWriter { + constructor(WritableStream stream); + + readonly attribute Promise<void> closed; + readonly attribute unrestricted double? desiredSize; + readonly attribute Promise<void> ready; + + Promise<void> abort(optional any reason); + Promise<void> close(); + void releaseLock(); + Promise<void> write(optional any chunk); +}; + + +

Internal slots

+ +Instances of {{WritableStreamDefaultWriter}} are created with the internal slots described in the +following table: + + + + + + + + +
Internal Slot + Description (non-normative) +
\[[closedPromise]] + A promise returned by the writer's + {{WritableStreamDefaultWriter/closed}} getter +
\[[ownerWritableStream]] + A {{WritableStream}} instance that owns this reader +
\[[readyPromise]] + A promise returned by the writer's + {{WritableStreamDefaultWriter/ready}} getter +
+ +

Constructor, methods, and properties

+ +
+
writer = new {{WritableStreamDefaultWriter(stream)|WritableStreamDefaultWriter}}(|stream|) +
+

This is equivalent to calling |stream|.{{WritableStream/getWriter()}}. + +

await writer.{{WritableStreamDefaultWriter/closed}} +
+

Returns a promise that will be fulfilled when the stream becomes closed, or rejected if the + stream ever errors or the writer's lock is [=release a write lock|released=] before the stream + finishes closing. + +

desiredSize = writer.{{WritableStreamDefaultWriter/desiredSize}} +
+

Returns the [=desired size to fill a stream's internal queue|desired size to fill the stream's + internal queue=]. It can be negative, if the queue is over-full. A [=producer=] can use this + information to determine the right amount of data to write. + +

It will be null if the stream cannot be successfully written to (due to either being errored, + or having an abort queued up). It will return zero if the stream is closed. And the getter will + throw an exception if invoked when the writer's lock is [=release a write lock|released=]. + +

await writer.{{WritableStreamDefaultWriter/ready}} +
+

Returns a promise that will be fulfilled when the [=desired size to fill a stream's internal + queue|desired size to fill the stream's internal queue=] transitions from non-positive to + positive, signaling that it is no longer applying [=backpressure=]. Once the [=desired size to + fill a stream's internal queue|desired size=] dips back to zero or below, the getter will return + a new promise that stays pending until the next transition. + +

If the stream becomes errored or aborted, or the writer's lock is [=release a write + lock|released=], the returned promise will become rejected. + +

await writer.{{WritableStreamDefaultWriter/abort(reason)|abort}}([ reason ]) +
+

If the reader is [=active writer|active=], behaves the same as + |stream|.{{WritableStream/abort(reason)|abort}}(reason). + +

await writer.{{WritableStreamDefaultWriter/close()|close}}() +
+

If the reader is [=active writer|active=], behaves the same as + |stream|.{{WritableStream/close()|close}}(). + +

writer.{{WritableStreamDefaultWriter/releaseLock()|releaseLock}}() +
+

[=release a write lock|Releases the writer's lock=] on the corresponding stream. After the lock + is released, the writer is no longer [=active writer|active=]. If the associated stream is errored + when the lock is released, the writer will appear errored in the same way from now on; otherwise, the writer will appear closed. - Note that the lock can still be released even if some ongoing writes have not yet finished (i.e. even if the promises - returned from previous calls to {{WritableStreamDefaultWriter/write()}} have not yet settled). It's not necessary to - hold the lock on the writer for the duration of the write; the lock instead simply prevents other producers - from writing in an interleaved manner. +

Note that the lock can still be released even if some ongoing writes have not yet finished + (i.e. even if the promises returned from previous calls to + {{WritableStreamDefaultWriter/write()}} have not yet settled). It's not necessary to hold the + lock on the writer for the duration of the write; the lock instead simply prevents other + [=producers=] from writing in an interleaved manner. + +

await writer.{{WritableStreamDefaultWriter/write(chunk)|write}}(chunk) +
+

Writes the given [=chunk=] to the writable stream, by waiting until any previous writes have + finished successfully, and then sending the [=chunk=] to the [=underlying sink=]'s + {{UnderlyingSink/write|write()}} method. It will return a promise that fulfills with undefined + upon a successful write, or rejects if the write fails or stream becomes errored before the + writing process is initiated. + +

Note that what "success" means is up to the [=underlying sink=]; it might indicate simply that + the [=chunk=] has been accepted, and not necessarily that it is safely saved to its ultimate + destination. +

+ +
+ The WritableStreamDefaultWriter(|stream|) constructor steps + are: + + 1. Perform ? [$SetUpWritableStreamDefaultWriter$]([=this=], |stream|).
- - 1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, throw a *TypeError* exception. - 1. Let _stream_ be *this*.[[ownerWritableStream]]. - 1. If _stream_ is *undefined*, return. - 1. Assert: _stream_.[[writer]] is not *undefined*. - 1. Perform ! WritableStreamDefaultWriterRelease(*this*). - +
+ The closed + getter steps are: -
write(chunk)
+ 1. Return [=this=].\[[closedPromise]]. +
-
- The write method writes the given chunk to the writable stream, by waiting until any previous - writes have finished successfully, and then sending the chunk to the underlying sink's {{underlying - sink/write()}} method. It will return a promise that fulfills with undefined upon a successful - write, or rejects if the write fails or stream becomes errored before the writing process is initiated. - - Note that what "success" means is up to the underlying sink; it might indicate simply that the chunk has - been accepted, and not necessarily that it is safely saved to its ultimate destination. -
- - - 1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, return a promise rejected with a *TypeError* exception. - 1. If *this*.[[ownerWritableStream]] is *undefined*, return a promise rejected with a *TypeError* exception. - 1. Return ! WritableStreamDefaultWriterWrite(*this*, _chunk_). - - -

Writable stream writer abstract operations

- -

IsWritableStreamDefaultWriter ( -x )

- - - 1. If Type(_x_) is not Object, return *false*. - 1. If _x_ does not have an [[ownerWritableStream]] internal slot, return *false*. - 1. Return *true*. - - -

WritableStreamDefaultWriterAbort ( writer, reason )

- - - 1. Let _stream_ be _writer_.[[ownerWritableStream]]. - 1. Assert: _stream_ is not *undefined*. - 1. Return ! WritableStreamAbort(_stream_, _reason_). - - -

WritableStreamDefaultWriterClose ( writer )

- - - 1. Let _stream_ be _writer_.[[ownerWritableStream]]. - 1. Assert: _stream_ is not *undefined*. - 1. Return ! WritableStreamClose(_stream_). - - -

WritableStreamDefaultWriterCloseWithErrorPropagation ( writer )

- -

This abstract operation helps implement the error propagation semantics of -{{ReadableStream/pipeTo()}}.

- - - 1. Let _stream_ be _writer_.[[ownerWritableStream]]. - 1. Assert: _stream_ is not *undefined*. - 1. Let _state_ be _stream_.[[state]]. - 1. If ! WritableStreamCloseQueuedOrInFlight(_stream_) is *true* or _state_ is `"closed"`, return - a promise resolved with *undefined*. - 1. If _state_ is `"errored"`, return a promise rejected with _stream_.[[storedError]]. - 1. Assert: _state_ is `"writable"` or `"erroring"`. - 1. Return ! WritableStreamDefaultWriterClose(_writer_). - - -

WritableStreamDefaultWriterEnsureClosedPromiseRejected( writer, error )

- - - 1. If _writer_.[[closedPromise]].[[PromiseState]] is `"pending"`, reject _writer_.[[closedPromise]] with - _error_. - 1. Otherwise, set _writer_.[[closedPromise]] to a promise rejected with _error_. - 1. Set _writer_.[[closedPromise]].[[PromiseIsHandled]] to *true*. - - -

WritableStreamDefaultWriterEnsureReadyPromiseRejected( writer, error )

- - - 1. If _writer_.[[readyPromise]].[[PromiseState]] is `"pending"`, reject _writer_.[[readyPromise]] with _error_. - 1. Otherwise, set _writer_.[[readyPromise]] to a promise rejected with _error_. - 1. Set _writer_.[[readyPromise]].[[PromiseIsHandled]] to *true*. - - -

WritableStreamDefaultWriterGetDesiredSize ( writer )

- - - 1. Let _stream_ be _writer_.[[ownerWritableStream]]. - 1. Let _state_ be _stream_.[[state]]. - 1. If _state_ is `"errored"` or `"erroring"`, return *null*. - 1. If _state_ is `"closed"`, return *0*. - 1. Return ! WritableStreamDefaultControllerGetDesiredSize(_stream_.[[writableStreamController]]). - - -

WritableStreamDefaultWriterRelease ( writer )

- - - 1. Let _stream_ be _writer_.[[ownerWritableStream]]. - 1. Assert: _stream_ is not *undefined*. - 1. Assert: _stream_.[[writer]] is _writer_. - 1. Let _releasedError_ be a new *TypeError*. - 1. Perform ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(_writer_, _releasedError_). - 1. Perform ! WritableStreamDefaultWriterEnsureClosedPromiseRejected(_writer_, _releasedError_). - 1. Set _stream_.[[writer]] to *undefined*. - 1. Set _writer_.[[ownerWritableStream]] to *undefined*. - - -

WritableStreamDefaultWriterWrite ( writer, chunk )

- - - 1. Let _stream_ be _writer_.[[ownerWritableStream]]. - 1. Assert: _stream_ is not *undefined*. - 1. Let _controller_ be _stream_.[[writableStreamController]]. - 1. Let _chunkSize_ be ! WritableStreamDefaultControllerGetChunkSize(_controller_, _chunk_). - 1. If _stream_ is not equal to _writer_.[[ownerWritableStream]], return a promise rejected with a *TypeError* - exception. - 1. Let _state_ be _stream_.[[state]]. - 1. If _state_ is `"errored"`, return a promise rejected with _stream_.[[storedError]]. - 1. If ! WritableStreamCloseQueuedOrInFlight(_stream_) is *true* or _state_ is `"closed"`, return - a promise rejected with a *TypeError* exception indicating that the stream is closing or closed. - 1. If _state_ is `"erroring"`, return a promise rejected with _stream_.[[storedError]]. - 1. Assert: _state_ is `"writable"`. - 1. Let _promise_ be ! WritableStreamAddWriteRequest(_stream_). - 1. Perform ! WritableStreamDefaultControllerWrite(_controller_, _chunk_, _chunkSize_). - 1. Return _promise_. - - -

Class -WritableStreamDefaultController

- -The {{WritableStreamDefaultController}} class has methods that allow control of a {{WritableStream}}'s state. When -constructing a {{WritableStream}}, the underlying sink is given a corresponding -{{WritableStreamDefaultController}} instance to manipulate. - -

Class definition

+
+ The desiredSize getter steps are: -
+ 1. If [=this=].\[[ownerWritableStream]] is undefined, throw a {{TypeError}} exception. + 1. Return ! [$WritableStreamDefaultWriterGetDesiredSize$]([=this=]). +
-This section is non-normative. +
+ The ready getter + steps are: -If one were to write the {{WritableStreamDefaultController}} class in something close to the syntax of [[!ECMASCRIPT]], -it would look like + 1. Return [=this=].\[[readyPromise]]. +
-

-  class WritableStreamDefaultController {
-    constructor() // always throws
+
+ The abort(|reason|) + method steps are: - error(e) - } -
+ 1. If [=this=].\[[ownerWritableStream]] is undefined, return [=a promise rejected with=] a + {{TypeError}} exception. + 1. Return ! [$WritableStreamDefaultWriterAbort$]([=this=], |reason|). +
+ +
+ The close() method + steps are: + + 1. Let |stream| be [=this=].\[[ownerWritableStream]]. + 1. If |stream| is undefined, return [=a promise rejected with=] a {{TypeError}} exception. + 1. If ! [$WritableStreamCloseQueuedOrInFlight$](|stream|) is true, return [=a promise rejected + with=] a {{TypeError}} exception. + 1. Return ! [$WritableStreamDefaultWriterClose$]([=this=]). +
+ +
+ The releaseLock() method steps are: + + 1. Let |stream| be [=this=].\[[ownerWritableStream]]. + 1. If |stream| is undefined, return. + 1. Assert: |stream|.\[[writer]] is not undefined. + 1. Perform ! [$WritableStreamDefaultWriterRelease$]([=this=]). +
+ +
+ The write(|chunk|) + method steps are: + 1. If [=this=].\[[ownerWritableStream]] is undefined, return [=a promise rejected with=] a + {{TypeError}} exception. + 1. Return ! [$WritableStreamDefaultWriterWrite$]([=this=], |chunk|).
+

The {{WritableStreamDefaultController}} class

+ +The {{WritableStreamDefaultController}} class has methods that allow control of a +{{WritableStream}}'s state. When constructing a {{WritableStream}}, the [=underlying sink=] is +given a corresponding {{WritableStreamDefaultController}} instance to manipulate. + +

Interface definition

+ +The Web IDL definition for the {{WritableStreamDefaultController}} class is given as follows: + + +[Exposed=(Window,Worker,Worklet)] +interface WritableStreamDefaultController { + void error(optional any e); +}; + +

Internal slots

-Instances of {{WritableStreamDefaultController}} are created with the internal slots described in the following table: +Instances of {{WritableStreamDefaultController}} are created with the internal slots described in +the following table: - - - - - - + + + + + - + - + - + - + - + - + - + - + - +
Internal SlotDescription (non-normative)
Internal SlotDescription (non-normative)
\[[abortAlgorithm]] - A promise-returning algorithm, taking one argument (the abort reason), which communicates - a requested abort to the underlying sink -
\[[abortAlgorithm]] + A promise-returning algorithm, taking one argument (the abort reason), + which communicates a requested abort to the [=underlying sink=]
\[[closeAlgorithm]] - A promise-returning algorithm which communicates a requested close to the underlying - sink -
\[[closeAlgorithm]] + A promise-returning algorithm which communicates a requested close to + the [=underlying sink=]
\[[controlledWritableStream]] - The {{WritableStream}} instance controlled -
\[[controlledWritableStream]] + The {{WritableStream}} instance controlled
\[[queue]] - A List representing the stream's internal queue of chunks -
\[[queue]] + A [=list=] representing the stream's internal queue of [=chunks=]
\[[queueTotalSize]] - The total size of all the chunks stored in \[[queue]] (see [[#queue-with-sizes]]) -
\[[queueTotalSize]] + The total size of all the chunks stored in \[[queue]] (see + [[#queue-with-sizes]])
\[[started]] - A boolean flag indicating whether the underlying sink has finished starting -
\[[started]] + A boolean flag indicating whether the [=underlying sink=] has finished + starting
\[[strategyHWM]] - A number supplied by the creator of the stream as part of the stream's queuing - strategy, indicating the point at which the stream will apply backpressure to its underlying - sink -
\[[strategyHWM]] + A number supplied by the creator of the stream as part of the stream's + [=queuing strategy=], indicating the point at which the stream will apply [=backpressure=] to its + [=underlying sink=]
\[[strategySizeAlgorithm]] - An algorithm to calculate the size of enqueued chunks, as part of the stream’s - queuing strategy -
\[[strategySizeAlgorithm]] + An algorithm to calculate the size of enqueued [=chunks=], as part of + the stream's [=queuing strategy=]
\[[writeAlgorithm]] - A promise-returning algorithm, taking one argument (the chunk to write), which - writes data to the underlying sink -
\[[writeAlgorithm]] + A promise-returning algorithm, taking one argument (the [=chunk=] to + write), which writes data to the [=underlying sink=]
-

new WritableStreamDefaultController()

+

Methods

-
- The WritableStreamDefaultController constructor cannot be used directly; - {{WritableStreamDefaultController}} instances are created automatically during {{WritableStream}} construction. +
+
controller.{{WritableStreamDefaultController/error()|error}}(e) +
+

Closes the controlled writable stream, making all future interactions with it fail with the + given error e. + +

This method is rarely used, since usually it suffices to return a rejected promise from one of + the [=underlying sink=]'s methods. However, it can be useful for suddenly shutting down a stream + in response to an event outside the normal lifecycle of interactions with the [=underlying + sink=]. +

+ +
+ The error(|e|) method steps are: + + 1. Let |state| be [=this=].\[[controlledWritableStream]].\[[state]]. + 1. If |state| is not "`writable`", return. + 1. Perform ! [$WritableStreamDefaultControllerError$]([=this=], |e|).
- - 1. Throw a *TypeError* exception. - +

Internal methods

-

Properties of the {{WritableStreamDefaultController}} prototype

+The following are internal methods implemented by each {{WritableStreamDefaultController}} instance. +The writable stream implementation will call into these. -
error(e)
+

The reason these are in method form, instead of as abstract operations, is to make +it clear that the writable stream implementation is decoupled from the controller implementation, +and could in the future be expanded with other controllers, as long as those controllers +implemented such internal methods. A similar scenario is seen for readable streams (see +[[#rs-abstract-ops-used-by-controllers]]), where there actually are multiple controller types and +as such the counterpart internal methods are used polymorphically. -

- The error method will error the writable stream, making all future interactions with it fail with the - given error e. - - This method is rarely used, since usually it suffices to return a rejected promise from one of the underlying - sink's methods. However, it can be useful for suddenly shutting down a stream in response to an event outside the - normal lifecycle of interactions with the underlying sink. -
- - - 1. If ! IsWritableStreamDefaultController(*this*) is *false*, throw a *TypeError* exception. - 1. Let _state_ be *this*.[[controlledWritableStream]].[[state]]. - 1. If _state_ is not `"writable"`, return. - 1. Perform ! WritableStreamDefaultControllerError(*this*, _e_). - - -

Writable stream default controller internal methods

- -The following are additional internal methods implemented by each {{WritableStreamDefaultController}} instance. The -writable stream implementation will call into these. - -

The reason these are in method form, instead of as abstract operations, is to make it clear that the -writable stream implementation is decoupled from the controller implementation, and could in the future be expanded with -other controllers, as long as those controllers implemented such internal methods. A similar scenario is seen for -readable streams, where there actually are multiple controller types and as such the counterpart internal methods are -used polymorphically. - -

\[[AbortSteps]]( -reason )
- - - 1. Let _result_ be the result of performing *this*.[[abortAlgorithm]], passing _reason_. - 1. Perform ! WritableStreamDefaultControllerClearAlgorithms(*this*). - 1. Return _result_. - - -
\[[ErrorSteps]]()
- - - 1. Perform ! ResetQueue(*this*). - - -

Writable stream default controller abstract operations

- -

IsWritableStreamDefaultController ( x )

- - - 1. If Type(_x_) is not Object, return *false*. - 1. If _x_ does not have an [[controlledWritableStream]] internal slot, return *false*. - 1. Return *true*. - - -

SetUpWritableStreamDefaultController ( stream, controller, startAlgorithm, -writeAlgorithm, closeAlgorithm, abortAlgorithm, highWaterMark, sizeAlgorithm )

- - - 1. Assert: ! IsWritableStream(_stream_) is *true*. - 1. Assert: _stream_.[[writableStreamController]] is *undefined*. - 1. Set _controller_.[[controlledWritableStream]] to _stream_. - 1. Set _stream_.[[writableStreamController]] to _controller_. - 1. Perform ! ResetQueue(_controller_). - 1. Set _controller_.[[started]] to *false*. - 1. Set _controller_.[[strategySizeAlgorithm]] to _sizeAlgorithm_. - 1. Set _controller_.[[strategyHWM]] to _highWaterMark_. - 1. Set _controller_.[[writeAlgorithm]] to _writeAlgorithm_. - 1. Set _controller_.[[closeAlgorithm]] to _closeAlgorithm_. - 1. Set _controller_.[[abortAlgorithm]] to _abortAlgorithm_. - 1. Let _backpressure_ be ! WritableStreamDefaultControllerGetBackpressure(_controller_). - 1. Perform ! WritableStreamUpdateBackpressure(_stream_, _backpressure_). - 1. Let _startResult_ be the result of performing _startAlgorithm_. (This may throw an exception.) - 1. Let _startPromise_ be a promise resolved with _startResult_. - 1. Upon fulfillment of _startPromise_, - 1. Assert: _stream_.[[state]] is `"writable"` or `"erroring"`. - 1. Set _controller_.[[started]] to *true*. - 1. Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(_controller_). - 1. Upon rejection of _startPromise_ with reason _r_, - 1. Assert: _stream_.[[state]] is `"writable"` or `"erroring"`. - 1. Set _controller_.[[started]] to *true*. - 1. Perform ! WritableStreamDealWithRejection(_stream_, _r_). - - -

SetUpWritableStreamDefaultControllerFromUnderlyingSink ( stream, underlyingSink, -highWaterMark, sizeAlgorithm )

- - - 1. Assert: _underlyingSink_ is not *undefined*. - 1. Let _controller_ be ObjectCreate(the original value of `WritableStreamDefaultController`'s `prototype` - property). - 1. Let _startAlgorithm_ be the following steps: - 1. Return ? InvokeOrNoop(_underlyingSink_, `"start"`, « _controller_ »). - 1. Let _writeAlgorithm_ be ? CreateAlgorithmFromUnderlyingMethod(_underlyingSink_, `"write"`, *1*, « _controller_ »). - 1. Let _closeAlgorithm_ be ? CreateAlgorithmFromUnderlyingMethod(_underlyingSink_, `"close"`, *0*, « »). - 1. Let _abortAlgorithm_ be ? CreateAlgorithmFromUnderlyingMethod(_underlyingSink_, `"abort"`, *1*, « »). - 1. Perform ? SetUpWritableStreamDefaultController(_stream_, _controller_, _startAlgorithm_, _writeAlgorithm_, - _closeAlgorithm_, _abortAlgorithm_, _highWaterMark_, _sizeAlgorithm_). - - -

WritableStreamDefaultControllerClearAlgorithms ( controller )

- -This abstract operation is called once the stream is closed or errored and the algorithms will not be executed any more. -By removing the algorithm references it permits the underlying sink object to be garbage collected even if the -{{WritableStream}} itself is still referenced. - -

The results of this algorithm are not currently observable, but could become so if JavaScript eventually -adds weak references. But even without that factor, -implementations will likely want to include similar steps.

- -

This operation will be performed multiple times in some edge cases. After the first time it will do -nothing.

- - - 1. Set _controller_.[[writeAlgorithm]] to *undefined*. - 1. Set _controller_.[[closeAlgorithm]] to *undefined*. - 1. Set _controller_.[[abortAlgorithm]] to *undefined*. - 1. Set _controller_.[[strategySizeAlgorithm]] to *undefined*. - - -

WritableStreamDefaultControllerClose ( controller )

- - - 1. Perform ! EnqueueValueWithSize(_controller_, `"close"`, *0*). - 1. Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(_controller_). - - -

WritableStreamDefaultControllerGetChunkSize ( controller, chunk )

- - - 1. Let _returnValue_ be the result of performing _controller_.[[strategySizeAlgorithm]], passing in _chunk_, and - interpreting the result as an ECMAScript completion value. - 1. If _returnValue_ is an abrupt completion, - 1. Perform ! WritableStreamDefaultControllerErrorIfNeeded(_controller_, _returnValue_.[[Value]]). - 1. Return 1. - 1. Return _returnValue_.[[Value]]. - - -

WritableStreamDefaultControllerGetDesiredSize ( controller )

- - - 1. Return _controller_.[[strategyHWM]] − _controller_.[[queueTotalSize]]. - - -

WritableStreamDefaultControllerWrite ( controller, chunk, chunkSize -)

- - - 1. Let _writeRecord_ be Record {[[chunk]]: _chunk_}. - 1. Let _enqueueResult_ be EnqueueValueWithSize(_controller_, _writeRecord_, _chunkSize_). - 1. If _enqueueResult_ is an abrupt completion, - 1. Perform ! WritableStreamDefaultControllerErrorIfNeeded(_controller_, _enqueueResult_.[[Value]]). - 1. Return. - 1. Let _stream_ be _controller_.[[controlledWritableStream]]. - 1. If ! WritableStreamCloseQueuedOrInFlight(_stream_) is *false* and _stream_.[[state]] is `"writable"`, - 1. Let _backpressure_ be ! WritableStreamDefaultControllerGetBackpressure(_controller_). - 1. Perform ! WritableStreamUpdateBackpressure(_stream_, _backpressure_). - 1. Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(_controller_). - - -

WritableStreamDefaultControllerAdvanceQueueIfNeeded ( -controller )

- - - 1. Let _stream_ be _controller_.[[controlledWritableStream]]. - 1. If _controller_.[[started]] is *false*, return. - 1. If _stream_.[[inFlightWriteRequest]] is not *undefined*, return. - 1. Let _state_ be _stream_.[[state]]. - 1. Assert: _state_ is not `"closed"` or `"errored"`. - 1. If _state_ is `"erroring"`, - 1. Perform ! WritableStreamFinishErroring(_stream_). - 1. Return. - 1. If _controller_.[[queue]] is empty, return. - 1. Let _writeRecord_ be ! PeekQueueValue(_controller_). - 1. If _writeRecord_ is `"close"`, perform ! WritableStreamDefaultControllerProcessClose(_controller_). - 1. Otherwise, perform ! WritableStreamDefaultControllerProcessWrite(_controller_, _writeRecord_.[[chunk]]). - - -

WritableStreamDefaultControllerErrorIfNeeded ( controller, error )

- - - 1. If _controller_.[[controlledWritableStream]].[[state]] is `"writable"`, perform ! - WritableStreamDefaultControllerError(_controller_, _error_). - - -

WritableStreamDefaultControllerProcessClose ( controller )

- - - 1. Let _stream_ be _controller_.[[controlledWritableStream]]. - 1. Perform ! WritableStreamMarkCloseRequestInFlight(_stream_). - 1. Perform ! DequeueValue(_controller_). - 1. Assert: _controller_.[[queue]] is empty. - 1. Let _sinkClosePromise_ be the result of performing _controller_.[[closeAlgorithm]]. - 1. Perform ! WritableStreamDefaultControllerClearAlgorithms(_controller_). - 1. Upon fulfillment of _sinkClosePromise_, - 1. Perform ! WritableStreamFinishInFlightClose(_stream_). - 1. Upon rejection of _sinkClosePromise_ with reason _reason_, - 1. Perform ! WritableStreamFinishInFlightCloseWithError(_stream_, _reason_). - - -

WritableStreamDefaultControllerProcessWrite ( controller, chunk )

- - - 1. Let _stream_ be _controller_.[[controlledWritableStream]]. - 1. Perform ! WritableStreamMarkFirstWriteRequestInFlight(_stream_). - 1. Let _sinkWritePromise_ be the result of performing _controller_.[[writeAlgorithm]], passing in _chunk_. - 1. Upon fulfillment of _sinkWritePromise_, - 1. Perform ! WritableStreamFinishInFlightWrite(_stream_). - 1. Let _state_ be _stream_.[[state]]. - 1. Assert: _state_ is `"writable"` or `"erroring"`. - 1. Perform ! DequeueValue(_controller_). - 1. If ! WritableStreamCloseQueuedOrInFlight(_stream_) is *false* and _state_ is `"writable"`, - 1. Let _backpressure_ be ! WritableStreamDefaultControllerGetBackpressure(_controller_). - 1. Perform ! WritableStreamUpdateBackpressure(_stream_, _backpressure_). - 1. Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(_controller_). - 1. Upon rejection of _sinkWritePromise_ with _reason_, - 1. If _stream_.[[state]] is `"writable"`, perform ! WritableStreamDefaultControllerClearAlgorithms(_controller_). - 1. Perform ! WritableStreamFinishInFlightWriteWithError(_stream_, _reason_). - - -

WritableStreamDefaultControllerGetBackpressure ( controller )

- - - 1. Let _desiredSize_ be ! WritableStreamDefaultControllerGetDesiredSize(_controller_). - 1. Return _desiredSize_ ≤ *0*. - - -

WritableStreamDefaultControllerError ( controller, error )

- - - 1. Let _stream_ be _controller_.[[controlledWritableStream]]. - 1. Assert: _stream_.[[state]] is `"writable"`. - 1. Perform ! WritableStreamDefaultControllerClearAlgorithms(_controller_). - 1. Perform ! WritableStreamStartErroring(_stream_, _error_). - +
+ \[[AbortSteps]](|reason|) implements the + [$WritableStreamController/[[AbortSteps]]$] contract. It performs the following steps: + + 1. Let |result| be the result of performing [=this=].\[[abortAlgorithm]], passing |reason|. + 1. Perform ! [$WritableStreamDefaultControllerClearAlgorithms$]([=this=]). + 1. Return |result|. +
+ +
+ \[[ErrorSteps]]() implements the + [$WritableStreamController/[[ErrorSteps]]$] contract. It performs the following steps: + + 1. Perform ! [$ResetQueue$]([=this=]). +
+ +

Abstract operations

+ +

Working with writable streams

+ +The following abstract operations operate on {{WritableStream}} instances at a higher level. Some +are even meant to be generally useful by other specifications. + +
+ AcquireWritableStreamDefaultWriter(|stream|) is meant to be called from other + specifications that wish to acquire a [=writer=] for a given writable stream. It performs the + following steps: + + 1. Let |writer| be a [=new=] {{WritableStreamDefaultWriter}}. + 1. Perform ? [$SetUpWritableStreamDefaultWriter$](|writer|, |stream|). + 1. Return |writer|. +
+ +
+ CreateWritableStream(|startAlgorithm|, |writeAlgorithm|, |closeAlgorithm|, + |abortAlgorithm|[, |highWaterMark|[, |sizeAlgorithm|]]) is meant to be called from other + specifications that wish to create {{WritableStream}} instances. The |writeAlgorithm|, + |closeAlgorithm|, and |abortAlgorithm| algorithms must return promises; if supplied, + |sizeAlgorithm| must be an algorithm accepting [=chunk=] objects and returning a number; and if + supplied, |highWaterMark| must be a non-negative, non-NaN number. + + It performs the following steps: + + 1. If |highWaterMark| was not passed, set it to 1. + 1. If |sizeAlgorithm| was not passed, set it to an algorithm that returns 1. + 1. Assert: ! [$IsNonNegativeNumber$](|highWaterMark|) is true. + 1. Let |stream| be a [=new=] {{WritableStream}}. + 1. Perform ! [$InitializeWritableStream$](|stream|). + 1. Let |controller| be a [=new=] {{WritableStreamDefaultController}}. + 1. Perform ? [$SetUpWritableStreamDefaultController$](|stream|, |controller|, |startAlgorithm|, + |writeAlgorithm|, |closeAlgorithm|, |abortAlgorithm|, |highWaterMark|, |sizeAlgorithm|). + 1. Return |stream|. + +

This abstract operation will throw an exception if and only if the supplied + |startAlgorithm| throws. +

+ +
+ InitializeWritableStream(|stream|) performs the following + steps: + + 1. Set |stream|.\[[state]] to "`writable`". + 1. Set |stream|.\[[storedError]], |stream|.\[[writer]], |stream|.\[[writableStreamController]], + |stream|.\[[inFlightWriteRequest]], |stream|.\[[closeRequest]], + |stream|.\[[inFlightCloseRequest]] and |stream|.\[[pendingAbortRequest]] to undefined. + 1. Set |stream|.\[[writeRequests]] to a new empty [=list=]. + 1. Set |stream|.\[[backpressure]] to false. +
+ +
+ IsWritableStreamLocked(|stream|) is meant to be called from + other specifications that wish to query whether or not a writable stream is [=locked to a writer=]. + It performs the following steps: + + 1. If |stream|.\[[writer]] is undefined, return false. + 1. Return true. +
+ +
+ SetUpWritableStreamDefaultWriter(|writer|, + |stream|) performs the following steps: + + 1. If ! [$IsWritableStreamLocked$](|stream|) is true, throw a {{TypeError}} exception. + 1. Set |writer|.\[[ownerWritableStream]] to |stream|. + 1. Set |stream|.\[[writer]] to |writer|. + 1. Let |state| be |stream|.\[[state]]. + 1. If |state| is "`writable`", + 1. If ! [$WritableStreamCloseQueuedOrInFlight$](|stream|) is false and |stream|.\[[backpressure]] + is true, set |writer|.\[[readyPromise]] to [=a new promise=]. + 1. Otherwise, set |writer|.\[[readyPromise]] to [=a promise resolved with=] undefined. + 1. Set |writer|.\[[closedPromise]] to [=a new promise=]. + 1. Otherwise, if |state| is "`erroring`", + 1. Set |writer|.\[[readyPromise]] to [=a promise rejected with=] |stream|.\[[storedError]]. + 1. Set |writer|.\[[readyPromise]].\[[PromiseIsHandled]] to true. + 1. Set |writer|.\[[closedPromise]] to [=a new promise=]. + 1. Otherwise, if |state| is "`closed`", + 1. Set |writer|.\[[readyPromise]] to [=a promise resolved with=] undefined. + 1. Set |writer|.\[[closedPromise]] to [=a promise resolved with=] undefined. + 1. Otherwise, + 1. Assert: |state| is "`errored`". + 1. Let |storedError| be |stream|.\[[storedError]]. + 1. Set |writer|.\[[readyPromise]] to [=a promise rejected with=] |storedError|. + 1. Set |writer|.\[[readyPromise]].\[[PromiseIsHandled]] to true. + 1. Set |writer|.\[[closedPromise]] to [=a promise rejected with=] |storedError|. + 1. Set |writer|.\[[closedPromise]].\[[PromiseIsHandled]] to true. +
+ +
+ WritableStreamAbort(|stream|, |reason|) performs the following + steps: + + 1. Let |state| be |stream|.\[[state]]. + 1. If |state| is "`closed"` or `"errored`", return [=a promise resolved with=] undefined. + 1. If |stream|.\[[pendingAbortRequest]] is not undefined, return + |stream|.\[[pendingAbortRequest]].\[[promise]]. + 1. Assert: |state| is "`writable"` or `"erroring`". + 1. Let |wasAlreadyErroring| be false. + 1. If |state| is "`erroring`", + 1. Set |wasAlreadyErroring| to true. + 1. Set |reason| to undefined. + 1. Let |promise| be [=a new promise=]. + 1. Set |stream|.\[[pendingAbortRequest]] to Record {\[[promise]]: |promise|, \[[reason]]: |reason|, + \[[wasAlreadyErroring]]: |wasAlreadyErroring|}. + 1. If |wasAlreadyErroring| is false, perform ! [$WritableStreamStartErroring$](|stream|, |reason|). + 1. Return |promise|. +
+ +
+ WritableStreamClose(|stream|) performs the following steps: + + 1. Let |state| be |stream|.\[[state]]. + 1. If |state| is "`closed"` or `"errored`", return [=a promise rejected with=] a {{TypeError}} + exception. + 1. Assert: |state| is "`writable"` or `"erroring`". + 1. Assert: ! [$WritableStreamCloseQueuedOrInFlight$](|stream|) is false. + 1. Let |promise| be [=a new promise=]. + 1. Set |stream|.\[[closeRequest]] to |promise|. + 1. Let |writer| be |stream|.\[[writer]]. + 1. If |writer| is not undefined, and |stream|.\[[backpressure]] is true, and |state| is + "`writable`", [=resolve=] |writer|.\[[readyPromise]] with undefined. + 1. Perform ! [$WritableStreamDefaultControllerClose$](|stream|.\[[writableStreamController]]). + 1. Return |promise|. +
+ +

Interfacing with controllers

+ +To allow future flexibility to add different writable stream behaviors (similar to the distinction +between default readable streams and [=readable byte streams=]), much of the internal state of a +[=writable stream=] is encapsulated by the {{WritableStreamDefaultController}} class. + +Each controller class defines two internal methods, which are called by the {{WritableStream}} +algorithms: + +
+
\[[AbortSteps]](reason) +
The controller's steps that run in reaction to the stream being [=abort a writable + stream|aborted=], used to clean up the state stored in the controller and inform the + [=underlying sink=]. + +
\[[ErrorSteps]]() +
The controller's steps that run in reaction to the stream being errored, used to clean up the + state stored in the controller. +
+ +(These are defined as internal methods, instead of as abstract operations, so that they can be +called polymorphically by the {{WritableStream}} algorithms, without having to branch on which type +of controller is present. This is a bit theoretical for now, given that only +{{WritableStreamDefaultController}} exists so far.) + +The rest of this section concerns abstract operations that go in the other direction: they are used +by the controller implementation to affect its associated {{WritableStream}} object. This +translates internal state changes of the controllerinto developer-facing results visible through +the {{WritableStream}}'s public API. + +
+ WritableStreamAddWriteRequest(|stream|) performs the + following steps: + + 1. Assert: ! [$IsWritableStreamLocked$](|stream|) is true. + 1. Assert: |stream|.\[[state]] is "`writable`". + 1. Let |promise| be [=a new promise=]. + 1. [=list/Append=] |promise| to |stream|.\[[writeRequests]]. + 1. Return |promise|. +
+ +
+ WritableStreamCloseQueuedOrInFlight(|stream|) + performs the following steps: + + 1. If |stream|.\[[closeRequest]] is undefined and |stream|.\[[inFlightCloseRequest]] is undefined, + return false. + 1. Return true. +
+ +
+ WritableStreamDealWithRejection(|stream|, |error|) + performs the following steps: + + 1. Let |state| be |stream|.\[[state]]. + 1. If |state| is "`writable`", + 1. Perform ! [$WritableStreamStartErroring$](|stream|, |error|). + 1. Return. + 1. Assert: |state| is "`erroring`". + 1. Perform ! [$WritableStreamFinishErroring$](|stream|). +
+ +
+ WritableStreamFinishErroring(|stream|, |reason|) + performs the following steps: + + 1. Assert: |stream|.\[[state]] is "`erroring`". + 1. Assert: ! [$WritableStreamHasOperationMarkedInFlight$](|stream|) is false. + 1. Set |stream|.\[[state]] to "`errored`". + 1. Perform ! |stream|.\[[writableStreamController]].\[[ErrorSteps]](). + 1. Let |storedError| be |stream|.\[[storedError]]. + 1. [=list/For each=] |writeRequest| of |stream|.\[[writeRequests]]: + 1. [=Reject=] |writeRequest| with |storedError|. + 1. Set |stream|.\[[writeRequests]] to an empty [=list=]. + 1. If |stream|.\[[pendingAbortRequest]] is undefined, + 1. Perform ! [$WritableStreamRejectCloseAndClosedPromiseIfNeeded$](|stream|). + 1. Return. + 1. Let |abortRequest| be |stream|.\[[pendingAbortRequest]]. + 1. Set |stream|.\[[pendingAbortRequest]] to undefined. + 1. If |abortRequest|.\[[wasAlreadyErroring]] is true, + 1. [=Reject=] |abortRequest|.\[[promise]] with |storedError|. + 1. Perform ! [$WritableStreamRejectCloseAndClosedPromiseIfNeeded$](|stream|). + 1. Return. + 1. Let |promise| be ! + stream.\[[writableStreamController]].\[[AbortSteps]](|abortRequest|.\[[reason]]). + 1. [=Upon fulfillment=] of |promise|, + 1. [=Resolve=] |abortRequest|.\[[promise]] with undefined. + 1. Perform ! [$WritableStreamRejectCloseAndClosedPromiseIfNeeded$](|stream|). + 1. [=Upon rejection=] of |promise| with reason |reason|, + 1. [=Reject=] |abortRequest|.\[[promise]] with |reason|. + 1. Perform ! [$WritableStreamRejectCloseAndClosedPromiseIfNeeded$](|stream|). +
+ +
+ WritableStreamFinishInFlightClose(|stream|) + performs the following steps: + + 1. Assert: |stream|.\[[inFlightCloseRequest]] is not undefined. + 1. [=Resolve=] |stream|.\[[inFlightCloseRequest]] with undefined. + 1. Set |stream|.\[[inFlightCloseRequest]] to undefined. + 1. Let |state| be |stream|.\[[state]]. + 1. Assert: |stream|.\[[state]] is "`writable"` or `"erroring`". + 1. If |state| is "`erroring`", + 1. Set |stream|.\[[storedError]] to undefined. + 1. If |stream|.\[[pendingAbortRequest]] is not undefined, + 1. [=Resolve=] |stream|.\[[pendingAbortRequest]].\[[promise]] with undefined. + 1. Set |stream|.\[[pendingAbortRequest]] to undefined. + 1. Set |stream|.\[[state]] to "`closed`". + 1. Let |writer| be |stream|.\[[writer]]. + 1. If |writer| is not undefined, [=resolve=] |writer|.\[[closedPromise]] with undefined. + 1. Assert: |stream|.\[[pendingAbortRequest]] is undefined. + 1. Assert: |stream|.\[[storedError]] is undefined. +
+ +
+ WritableStreamFinishInFlightCloseWithError(|stream|, + |error|) performs the following steps: + + 1. Assert: |stream|.\[[inFlightCloseRequest]] is not undefined. + 1. [=Reject=] |stream|.\[[inFlightCloseRequest]] with |error|. + 1. Set |stream|.\[[inFlightCloseRequest]] to undefined. + 1. Assert: |stream|.\[[state]] is "`writable"` or `"erroring`". + 1. If |stream|.\[[pendingAbortRequest]] is not undefined, + 1. [=Reject=] |stream|.\[[pendingAbortRequest]].\[[promise]] with |error|. + 1. Set |stream|.\[[pendingAbortRequest]] to undefined. + 1. Perform ! WritableStreamDealWithRejection(|stream|, |error|). +
+ +
+ WritableStreamFinishInFlightWrite(|stream|) + performs the following steps: + + 1. Assert: |stream|.\[[inFlightWriteRequest]] is not undefined. + 1. [=Resolve=] |stream|.\[[inFlightWriteRequest]] with undefined. + 1. Set |stream|.\[[inFlightWriteRequest]] to undefined. +
+ +
+ WritableStreamFinishInFlightWriteWithError(|stream|, + |error|) performs the following steps: + + 1. Assert: |stream|.\[[inFlightWriteRequest]] is not undefined. + 1. [=Reject=] |stream|.\[[inFlightWriteRequest]] with |error|. + 1. Set |stream|.\[[inFlightWriteRequest]] to undefined. + 1. Assert: |stream|.\[[state]] is "`writable"` or `"erroring`". + 1. Perform ! [$WritableStreamDealWithRejection$](|stream|, |error|). +
+ +
+ WritableStreamHasOperationMarkedInFlight(|stream|) + performs the following steps: + + 1. If |stream|.\[[inFlightWriteRequest]] is undefined and + |stream|.\[[writableStreamController]].\[[inFlightCloseRequest]] is undefined, return false. + 1. Return true. +
+ +
+ WritableStreamMarkCloseRequestInFlight(|stream|) + performs the following steps: + + 1. Assert: |stream|.\[[inFlightCloseRequest]] is undefined. + 1. Assert: |stream|.\[[closeRequest]] is not undefined. + 1. Set |stream|.\[[inFlightCloseRequest]] to |stream|.\[[closeRequest]]. + 1. Set |stream|.\[[closeRequest]] to undefined. +
+ +
+ WritableStreamMarkFirstWriteRequestInFlight(|stream|) + performs the following steps: + + 1. Assert: |stream|.\[[inFlightWriteRequest]] is undefined. + 1. Assert: |stream|.\[[writeRequests]] is not empty. + 1. Let |writeRequest| be |stream|.\[[writeRequests]][0]. + 1. [=list/Remove=] |writeRequest| from |stream|.\[[writeRequests]]. + 1. Set |stream|.\[[inFlightWriteRequest]] to |writeRequest|. +
+ +
+ WritableStreamRejectCloseAndClosedPromiseIfNeeded(|stream|) + performs the following steps: + + 1. Assert: |stream|.\[[state]] is "`errored`". + 1. If |stream|.\[[closeRequest]] is not undefined, + 1. Assert: |stream|.\[[inFlightCloseRequest]] is undefined. + 1. [=Reject=] |stream|.\[[closeRequest]] with |stream|.\[[storedError]]. + 1. Set |stream|.\[[closeRequest]] to undefined. + 1. Let |writer| be |stream|.\[[writer]]. + 1. If |writer| is not undefined, + 1. [=Reject=] |writer|.\[[closedPromise]] with |stream|.\[[storedError]]. + 1. Set |writer|.\[[closedPromise]].\[[PromiseIsHandled]] to true. +
+ +
+ WritableStreamStartErroring(|stream|, |reason|) + performs the following steps: + + 1. Assert: |stream|.\[[storedError]] is undefined. + 1. Assert: |stream|.\[[state]] is "`writable`". + 1. Let |controller| be |stream|.\[[writableStreamController]]. + 1. Assert: |controller| is not undefined. + 1. Set |stream|.\[[state]] to "`erroring`". + 1. Set |stream|.\[[storedError]] to |reason|. + 1. Let |writer| be |stream|.\[[writer]]. + 1. If |writer| is not undefined, perform ! + [$WritableStreamDefaultWriterEnsureReadyPromiseRejected$](|writer|, |reason|). + 1. If ! [$WritableStreamHasOperationMarkedInFlight$](|stream|) is false and + |controller|.\[[started]] is true, perform ! [$WritableStreamFinishErroring$](|stream|). +
+ +
+ WritableStreamUpdateBackpressure(|stream|, + |backpressure|) performs the following steps: + + 1. Assert: |stream|.\[[state]] is "`writable`". + 1. Assert: ! [$WritableStreamCloseQueuedOrInFlight$](|stream|) is false. + 1. Let |writer| be |stream|.\[[writer]]. + 1. If |writer| is not undefined and |backpressure| is not |stream|.\[[backpressure]], + 1. If |backpressure| is true, set |writer|.\[[readyPromise]] to [=a new promise=]. + 1. Otherwise, + 1. Assert: |backpressure| is false. + 1. [=Resolve=] |writer|.\[[readyPromise]] with undefined. + 1. Set |stream|.\[[backpressure]] to |backpressure|. +
+ +

Writers

+ +The following abstract operations support the implementation and manipulation of +{{WritableStreamDefaultWriter}} instances. + +
+ WritableStreamDefaultWriterAbort(|writer|, + |reason|) performs the following steps: + + 1. Let |stream| be |writer|.\[[ownerWritableStream]]. + 1. Assert: |stream| is not undefined. + 1. Return ! [$WritableStreamAbort$](|stream|, |reason|). +
+ +
+ WritableStreamDefaultWriterClose(|writer|) performs + the following steps: + + 1. Let |stream| be |writer|.\[[ownerWritableStream]]. + 1. Assert: |stream| is not undefined. + 1. Return ! [$WritableStreamClose$](|stream|). +
+ +
+ WritableStreamDefaultWriterCloseWithErrorPropagation(|writer|) + performs the following steps: + + 1. Let |stream| be |writer|.\[[ownerWritableStream]]. + 1. Assert: |stream| is not undefined. + 1. Let |state| be |stream|.\[[state]]. + 1. If ! [$WritableStreamCloseQueuedOrInFlight$](|stream|) is true or |state| is "`closed`", return + [=a promise resolved with=] undefined. + 1. If |state| is "`errored`", return [=a promise rejected with=] |stream|.\[[storedError]]. + 1. Assert: |state| is "`writable"` or `"erroring`". + 1. Return ! [$WritableStreamDefaultWriterClose$](|writer|). + +

This abstract operation helps implement the error propagation semantics of + {{ReadableStream}}'s {{ReadableStream/pipeTo()}}. +

+ +
+ WritableStreamDefaultWriterEnsureClosedPromiseRejected(|writer|, + |error|) performs the following steps: + + 1. If |writer|.\[[closedPromise]].\[[PromiseState]] is "`pending`", [=reject=] + |writer|.\[[closedPromise]] with |error|. + 1. Otherwise, set |writer|.\[[closedPromise]] to [=a promise rejected with=] |error|. + 1. Set |writer|.\[[closedPromise]].\[[PromiseIsHandled]] to true. +
+ +
+ WritableStreamDefaultWriterEnsureReadyPromiseRejected(|writer|, + |error|) performs the following steps: + + 1. If |writer|.\[[readyPromise]].\[[PromiseState]] is "`pending`", [=reject=] + |writer|.\[[readyPromise]] with |error|. + 1. Otherwise, set |writer|.\[[readyPromise]] to [=a promise rejected with=] |error|. + 1. Set |writer|.\[[readyPromise]].\[[PromiseIsHandled]] to true. +
+ +
+ WritableStreamDefaultWriterGetDesiredSize(|writer|) + performs the following steps: + + 1. Let |stream| be |writer|.\[[ownerWritableStream]]. + 1. Let |state| be |stream|.\[[state]]. + 1. If |state| is "`errored"` or `"erroring`", return null. + 1. If |state| is "`closed`", return 0. + 1. Return ! + [$WritableStreamDefaultControllerGetDesiredSize$](|stream|.\[[writableStreamController]]). +
+ +
+ WritableStreamDefaultWriterRelease(|writer|) + performs the following steps: + + 1. Let |stream| be |writer|.\[[ownerWritableStream]]. + 1. Assert: |stream| is not undefined. + 1. Assert: |stream|.\[[writer]] is |writer|. + 1. Let |releasedError| be a new {{TypeError}}. + 1. Perform ! [$WritableStreamDefaultWriterEnsureReadyPromiseRejected$](|writer|, |releasedError|). + 1. Perform ! [$WritableStreamDefaultWriterEnsureClosedPromiseRejected$](|writer|, |releasedError|). + 1. Set |stream|.\[[writer]] to undefined. + 1. Set |writer|.\[[ownerWritableStream]] to undefined. +
+ +
+ WritableStreamDefaultWriterWrite(|writer|, |chunk|) + performs the following steps: + + 1. Let |stream| be |writer|.\[[ownerWritableStream]]. + 1. Assert: |stream| is not undefined. + 1. Let |controller| be |stream|.\[[writableStreamController]]. + 1. Let |chunkSize| be ! [$WritableStreamDefaultControllerGetChunkSize$](|controller|, |chunk|). + 1. If |stream| is not equal to |writer|.\[[ownerWritableStream]], return [=a promise rejected + with=] a {{TypeError}} exception. + 1. Let |state| be |stream|.\[[state]]. + 1. If |state| is "`errored`", return [=a promise rejected with=] |stream|.\[[storedError]]. + 1. If ! [$WritableStreamCloseQueuedOrInFlight$](|stream|) is true or |state| is "`closed`", return + [=a promise rejected with=] a {{TypeError}} exception indicating that the stream is closing or + closed. + 1. If |state| is "`erroring`", return [=a promise rejected with=] |stream|.\[[storedError]]. + 1. Assert: |state| is "`writable`". + 1. Let |promise| be ! [$WritableStreamAddWriteRequest$](|stream|). + 1. Perform ! [$WritableStreamDefaultControllerWrite$](|controller|, |chunk|, |chunkSize|). + 1. Return |promise|. +
+ +

Default controllers

+ +The following abstract operations support the implementation of the +{{WritableStreamDefaultController}} class. + + +
+ SetUpWritableStreamDefaultController(|stream|, + |controller|, |startAlgorithm|, |writeAlgorithm|, |closeAlgorithm|, |abortAlgorithm|, + |highWaterMark|, |sizeAlgorithm|) performs the following steps: + + 1. Assert: |stream| [=implements=] {{WritableStream}}. + 1. Assert: |stream|.\[[writableStreamController]] is undefined. + 1. Set |controller|.\[[controlledWritableStream]] to |stream|. + 1. Set |stream|.\[[writableStreamController]] to |controller|. + 1. Perform ! [$ResetQueue$](|controller|). + 1. Set |controller|.\[[started]] to false. + 1. Set |controller|.\[[strategySizeAlgorithm]] to |sizeAlgorithm|. + 1. Set |controller|.\[[strategyHWM]] to |highWaterMark|. + 1. Set |controller|.\[[writeAlgorithm]] to |writeAlgorithm|. + 1. Set |controller|.\[[closeAlgorithm]] to |closeAlgorithm|. + 1. Set |controller|.\[[abortAlgorithm]] to |abortAlgorithm|. + 1. Let |backpressure| be ! [$WritableStreamDefaultControllerGetBackpressure$](|controller|). + 1. Perform ! [$WritableStreamUpdateBackpressure$](|stream|, |backpressure|). + 1. Let |startResult| be the result of performing |startAlgorithm|. (This may throw an exception.) + 1. Let |startPromise| be [=a promise resolved with=] |startResult|. + 1. [=Upon fulfillment=] of |startPromise|, + 1. Assert: |stream|.\[[state]] is "`writable"` or `"erroring`". + 1. Set |controller|.\[[started]] to true. + 1. Perform ! [$WritableStreamDefaultControllerAdvanceQueueIfNeeded$](|controller|). + 1. [=Upon rejection=] of |startPromise| with reason |r|, + 1. Assert: |stream|.\[[state]] is "`writable"` or `"erroring`". + 1. Set |controller|.\[[started]] to true. + 1. Perform ! [$WritableStreamDealWithRejection$](|stream|, |r|). +
+ +
+ SetUpWritableStreamDefaultControllerFromUnderlyingSink(|stream|, + |underlyingSink|, |underlyingSinkDict|, |highWaterMark|, |sizeAlgorithm|) performs the + following steps: + + 1. Let |controller| be a [=new=] {{WritableStreamDefaultController}}. + 1. Let |startAlgorithm| be an algorithm that returns undefined. + 1. Let |writeAlgorithm| be an algorithm that returns [=a promise resolved with=] undefined. + 1. Let |closeAlgorithm| be an algorithm that returns [=a promise resolved with=] undefined. + 1. Let |abortAlgorithm| be an algorithm that returns [=a promise resolved with=] undefined. + 1. If |underlyingSinkDict|["{{UnderlyingSink/start}}"] [=map/exists=], then set |startAlgorithm| to + an algorithm which returns the result of [=invoking=] + |underlyingSinkDict|["{{UnderlyingSink/start}}"] with argument list « |controller| » + and [=callback this value=] |underlyingSink|. + 1. If |underlyingSinkDict|["{{UnderlyingSink/write}}"] [=map/exists=], then set |writeAlgorithm| to + an algorithm which takes an argument |chunk| and returns the result of [=invoking=] + |underlyingSinkDict|["{{UnderlyingSink/write}}"] with argument list « |chunk|, + |controller| » and [=callback this value=] |underlyingSink|. + 1. If |underlyingSinkDict|["{{UnderlyingSink/close}}"] [=map/exists=], then set |closeAlgorithm| to + an algorithm which returns the result of [=invoking=] + |underlyingSinkDict|["{{UnderlyingSink/close}}"] with argument list «» and [=callback this + value=] |underlyingSink|. + 1. If |underlyingSinkDict|["{{UnderlyingSink/abort}}"] [=map/exists=], then set |abortAlgorithm| to + an algorithm which takes an argument |reason| and returns the result of [=invoking=] + |underlyingSinkDict|["{{UnderlyingSink/abort}}"] with argument list « |reason| » and + [=callback this value=] |underlyingSink|. + 1. Perform ? [$SetUpWritableStreamDefaultController$](|stream|, |controller|, |startAlgorithm|, + |writeAlgorithm|, |closeAlgorithm|, |abortAlgorithm|, |highWaterMark|, |sizeAlgorithm|). +
+ +
+ WritableStreamDefaultControllerAdvanceQueueIfNeeded(|controller|) + performs the following steps: + + 1. Let |stream| be |controller|.\[[controlledWritableStream]]. + 1. If |controller|.\[[started]] is false, return. + 1. If |stream|.\[[inFlightWriteRequest]] is not undefined, return. + 1. Let |state| be |stream|.\[[state]]. + 1. Assert: |state| is not "`closed"` or `"errored`". + 1. If |state| is "`erroring`", + 1. Perform ! [$WritableStreamFinishErroring$](|stream|). + 1. Return. + 1. If |controller|.\[[queue]] is empty, return. + 1. Let |writeRecord| be ! [$PeekQueueValue$](|controller|). + 1. If |writeRecord| is "`close`", perform ! + [$WritableStreamDefaultControllerProcessClose$](|controller|). + 1. Otherwise, perform ! [$WritableStreamDefaultControllerProcessWrite$](|controller|, + |writeRecord|.\[[chunk]]). +
+ +
+ WritableStreamDefaultControllerClearAlgorithms(|controller|) + is called once the stream is closed or errored and the algorithms will not be executed any more. By + removing the algorithm references it permits the [=underlying sink=] object to be garbage + collected even if the {{WritableStream}} itself is still referenced. + +

This is observable using weak + references. See tc39/proposal-weakrefs#31 for more + detail. + + It performs the following steps: + + 1. Set |controller|.\[[writeAlgorithm]] to undefined. + 1. Set |controller|.\[[closeAlgorithm]] to undefined. + 1. Set |controller|.\[[abortAlgorithm]] to undefined. + 1. Set |controller|.\[[strategySizeAlgorithm]] to undefined. + +

This algorithm will be performed multiple times in some edge cases. After the first + time it will do nothing. +

+ +
+ WritableStreamDefaultControllerClose(|controller|) + performs the following steps: + + 1. Perform ! [$EnqueueValueWithSize$](|controller|, "`close`", 0). + 1. Perform ! [$WritableStreamDefaultControllerAdvanceQueueIfNeeded$](|controller|). +
+ +
+ WritableStreamDefaultControllerError(|controller|, + |error|) performs the following steps: + + 1. Let |stream| be |controller|.\[[controlledWritableStream]]. + 1. Assert: |stream|.\[[state]] is `"writable"`. + 1. Perform ! [$WritableStreamDefaultControllerClearAlgorithms$](|controller|). + 1. Perform ! [$WritableStreamStartErroring$](|stream|, |error|). +
+ +
+ WritableStreamDefaultControllerErrorIfNeeded(|controller|, + |error|) performs the following steps: + + 1. If |controller|.\[[controlledWritableStream]].\[[state]] is "`writable`", perform ! + [$WritableStreamDefaultControllerError$](|controller|, |error|). +
+ +
+ WritableStreamDefaultControllerGetBackpressure(|controller|) + performs the following steps: + + 1. Let |desiredSize| be ! [$WritableStreamDefaultControllerGetDesiredSize$](|controller|). + 1. Return true if |desiredSize| ≤ 0, or false otherwise. +
+ +
+ WritableStreamDefaultControllerGetChunkSize(|controller|, + |chunk|) performs the following steps: + + 1. Let |returnValue| be the result of performing |controller|.\[[strategySizeAlgorithm]], passing + in |chunk|, and interpreting the result as a [=completion record=]. + 1. If |returnValue| is an abrupt completion, + 1. Perform ! [$WritableStreamDefaultControllerErrorIfNeeded$](|controller|, + |returnValue|.\[[Value]]). + 1. Return 1. + 1. Return |returnValue|.\[[Value]]. +
+ +
+ WritableStreamDefaultControllerGetDesiredSize(|controller|) + performs the following steps: + + 1. Return |controller|.\[[strategyHWM]] − |controller|.\[[queueTotalSize]]. +
+ +
+ WritableStreamDefaultControllerProcessClose(|controller|) + performs the following steps: + + 1. Let |stream| be |controller|.\[[controlledWritableStream]]. + 1. Perform ! [$WritableStreamMarkCloseRequestInFlight$](|stream|). + 1. Perform ! [$DequeueValue$](|controller|). + 1. Assert: |controller|.\[[queue]] is empty. + 1. Let |sinkClosePromise| be the result of performing |controller|.\[[closeAlgorithm]]. + 1. Perform ! [$WritableStreamDefaultControllerClearAlgorithms$](|controller|). + 1. [=Upon fulfillment=] of |sinkClosePromise|, + 1. Perform ! [$WritableStreamFinishInFlightClose$](|stream|). + 1. [=Upon rejection=] of |sinkClosePromise| with reason |reason|, + 1. Perform ! [$WritableStreamFinishInFlightCloseWithError$](|stream|, |reason|). +
+ +
+ WritableStreamDefaultControllerProcessWrite(|controller|, + |chunk|) performs the following steps: + + 1. Let |stream| be |controller|.\[[controlledWritableStream]]. + 1. Perform ! [$WritableStreamMarkFirstWriteRequestInFlight$](|stream|). + 1. Let |sinkWritePromise| be the result of performing |controller|.\[[writeAlgorithm]], passing in + |chunk|. + 1. [=Upon fulfillment=] of |sinkWritePromise|, + 1. Perform ! [$WritableStreamFinishInFlightWrite$](|stream|). + 1. Let |state| be |stream|.\[[state]]. + 1. Assert: |state| is "`writable"` or `"erroring`". + 1. Perform ! [$DequeueValue$](|controller|). + 1. If ! [$WritableStreamCloseQueuedOrInFlight$](|stream|) is false and |state| is "`writable`", + 1. Let |backpressure| be ! [$WritableStreamDefaultControllerGetBackpressure$](|controller|). + 1. Perform ! [$WritableStreamUpdateBackpressure$](|stream|, |backpressure|). + 1. Perform ! [$WritableStreamDefaultControllerAdvanceQueueIfNeeded$](|controller|). + 1. [=Upon rejection=] of |sinkWritePromise| with |reason|, + 1. If |stream|.\[[state]] is "`writable`", perform ! + [$WritableStreamDefaultControllerClearAlgorithms$](|controller|). + 1. Perform ! [$WritableStreamFinishInFlightWriteWithError$](|stream|, |reason|). +
+ +
+ WritableStreamDefaultControllerWrite(|controller|, + |chunk|, |chunkSize|) performs the following steps: + + 1. Let |writeRecord| be Record {\[[chunk]]: |chunk|}. + 1. Let |enqueueResult| be [$EnqueueValueWithSize$](|controller|, |writeRecord|, |chunkSize|). + 1. If |enqueueResult| is an abrupt completion, + 1. Perform ! [$WritableStreamDefaultControllerErrorIfNeeded$](|controller|, + |enqueueResult|.\[[Value]]). + 1. Return. + 1. Let |stream| be |controller|.\[[controlledWritableStream]]. + 1. If ! [$WritableStreamCloseQueuedOrInFlight$](|stream|) is false and |stream|.\[[state]] is + "`writable`", + 1. Let |backpressure| be ! [$WritableStreamDefaultControllerGetBackpressure$](|controller|). + 1. Perform ! [$WritableStreamUpdateBackpressure$](|stream|, |backpressure|). + 1. Perform ! [$WritableStreamDefaultControllerAdvanceQueueIfNeeded$](|controller|). +

Transform streams

Using transform streams

-
- The natural way to use a transform stream is to place it in a pipe between a readable stream - and a writable stream. Chunks that travel from the readable stream to the writable stream - will be transformed as they pass through the transform stream. Backpressure is respected, so data will not be - read faster than it can be transformed and consumed. +
+ The natural way to use a transform stream is to place it in a [=piping|pipe=] between a [=readable + stream=] and a [=writable stream=]. [=Chunks=] that travel from the [=readable stream=] to the + [=writable stream=] will be transformed as they pass through the transform stream. + [=Backpressure=] is respected, so data will not be read faster than it can be transformed and + consumed. + + + readableStream + .pipeThrough(transformStream) + .pipeTo(writableStream) + .then(() => console.log("All data successfully transformed!")) + .catch(e => console.error("Something went wrong!", e)); + +
+ +
+ You can also use the {{TransformStream/readable}} and {{TransformStream/writable}} properties of a + transform stream directly to access the usual interfaces of a [=readable stream=] and [=writable + stream=]. In this example we supply data to the [=writable side=] of the stream using its + [=writer=] interface. The [=readable side=] is then piped to + anotherWritableStream. + + + const writer = transformStream.writable.getWriter(); + writer.write("input chunk"); + transformStream.readable.pipeTo(anotherWritableStream); + +
+ +
+ One use of [=identity transform streams=] is to easily convert between readable and writable + streams. For example, the {{fetch()}} API accepts a readable stream [=request/body|request body=], + but it can be more convenient to write data for uploading via a writable stream interface. Using + an identity transform stream addresses this: + + + const { writable, readable } = new TransformStream(); + fetch("...", { body: readable }).then(response => /* ... */); + + const writer = writable.getWriter(); + writer.write(new Uint8Array([0x73, 0x74, 0x72, 0x65, 0x61, 0x6D, 0x73, 0x21])); + writer.close(); + + + Another use of identity transform streams is to add additional buffering to a [=pipe=]. In this + example we add extra buffering between readableStream and + writableStream. + + + const writableStrategy = new ByteLengthQueuingStrategy({ highWaterMark: 1024 * 1024 }); + + readableStream + .pipeThrough(new TransformStream(undefined, writableStrategy)) + .pipeTo(writableStream); + +
+ +

The {{TransformStream}} class

+ +The {{TransformStream}} class is a concrete instance of the general [=transform stream=] concept. + +

Interface definition

+ +The Web IDL definition for the {{TransformStream}} class is given as follows: + + +[Exposed=(Window,Worker,Worklet)] +interface TransformStream { + constructor(optional object transformer, + optional QueuingStrategy writableStrategy = {}, + optional QueuingStrategy readableStrategy = {}); + + readonly attribute ReadableStream readable; + readonly attribute WritableStream writable; +}; + + +

Internal slots

+ +Instances of {{TransformStream}} are created with the internal slots described in the following +table: + + + + + + + + + + + + +
Internal SlotDescription (non-normative)
\[[backpressure]] + Whether there was backpressure on \[[readable]] the last time it was + observed +
\[[backpressureChangePromise]] + A promise which is fulfilled and replaced every time the value of + \[[backpressure]] changes +
\[[readable]] + The {{ReadableStream}} instance controlled by this object +
\[[transformStreamController]] + A {{TransformStreamDefaultController}} created with the ability to + control \[[readable]] and \[[writable]] +
\[[writable]] + The {{WritableStream}} instance controlled by this object +
+ +

The transformer API

-

-    readableStream
-      .pipeThrough(transformStream)
-      .pipeTo(writableStream)
-      .then(() => console.log("All data successfully transformed!"))
-      .catch(e => console.error("Something went wrong!", e));
-  
-
+The {{TransformStream()}} constructor accepts as its first argument a JavaScript object representing +the [=transformer=]. Such objects can contain any of the following methods: -
- You can also use the {{TransformStream/readable}} and {{TransformStream/writable}} properties of a transform stream - directly to access the usual interfaces of a readable stream and writable stream. In this example we - supply data to the writable side of the stream using its writer interface. The readable side is - then piped to anotherWritableStream. + +dictionary Transformer { + TransformerStartCallback start; + TransformerTransformCallback transform; + TransformerFlushCallback flush; + any readableType; + any writableType; +}; - <pre><code class="lang-javascript"> - const writer = transformStream.writable.getWriter(); - writer.write("input chunk"); - transformStream.readable.pipeTo(anotherWritableStream); - </code></pre> -</div> +callback TransformerStartCallback = any (TransformStreamDefaultController controller); +callback TransformerFlushCallback = Promise<void> (TransformStreamDefaultController controller); +callback TransformerTransformCallback = Promise<void> (TransformStreamDefaultController controller, optional any chunk); + -
- One use of identity transform streams is to easily convert between readable and writable streams. For example, - the {{fetch()}} API accepts a readable stream request body, but it can be more - convenient to write data for uploading via a writable stream interface. Using an identity transform stream addresses - this: +
+
start(controller)
+
+

A function that is called immediately during creation of the {{TransformStream}}. + +

Typically this is used to enqueue prefix [=chunks=], using + {{TransformStreamDefaultController/enqueue()|controller.enqueue()}}. Those chunks will be read + from the [=readable side=] but don't depend on any writes to the [=writable side=]. + +

If this initial process is asynchronous, for example because it takes some effort to acquire + the prefix chunks, the function can return a promise to signal success or failure; a rejected + promise will error the stream. Any thrown exceptions will be re-thrown by the + {{TransformStream()}} constructor. + +

transform(chunk, controller)
+
+

A function called when a new [=chunk=] originally written to the [=writable side=] is ready to + be transformed. The stream implementation guarantees that this function will be called only after + previous transforms have succeeded, and never before {{Transformer/start|start()}} has completed + or after {{Transformer/flush|flush()}} has been called. + +

This function performs the actual transformation work of the transform stream. It can enqueue + the results using {{TransformStreamDefaultController/enqueue()|controller.enqueue()}}. This + permits a single chunk written to the writable side to result in zero or multiple chunks on the + [=readable side=], depending on how many times + {{TransformStreamDefaultController/enqueue()|controller.enqueue()}} is called. + [[#example-ts-lipfuzz]] demonstrates this by sometimes enqueuing zero chunks. + +

If the process of transforming is asynchronous, this function can return a promise to signal + success or failure of the transformation. A rejected promise will error both the readable and + writable sides of the transform stream. + +

If no {{Transformer/transform|transform()}} method is supplied, the identity transform is + used, which enqueues chunks unchanged from the writable side to the readable side. + +

flush(controller)
+
+

A function called after all [=chunks=] written to the [=writable side=] have been transformed + by successfully passing through {{Transformer/transform|transform()}}, and the writable side is + about to be closed. + +

Typically this is used to enqueue suffix chunks to the [=readable side=], before that too + becomes closed. An example can be seen in [[#example-ts-lipfuzz]]. + +

If the flushing process is asynchronous, the function can return a promise to signal success + or failure; the result will be communicated to the caller of + {{WritableStreamDefaultWriter/write()|stream.writable.write()}}. Additionally, a rejected + promise will error both the readable and writable sides of the stream. Throwing an exception is + treated the same as returning a rejected promise. + +

(Note that there is no need to call + {{TransformStreamDefaultController/terminate()|controller.terminate()}} inside + {{Transformer/flush|flush()}}; the stream is already in the process of successfully closing down, + and terminating it would be counterproductive.) + +

readableType
+
+

This property is reserved for future use, so any attempts to supply a value will throw an + exception. -


-    const { writable, readable } = new TransformStream();
-    fetch("...", { body: readable }).then(response => /* ... */);
+ 
writableType
+
+

This property is reserved for future use, so any attempts to supply a value will throw an + exception. +

- const writer = writable.getWriter(); - writer.write(new Uint8Array([0x73, 0x74, 0x72, 0x65, 0x61, 0x6D, 0x73, 0x21])); - writer.close(); -
+The controller object passed to {{Transformer/start|start()}}, +{{Transformer/transform|transform()}}, and {{Transformer/flush|flush()}} is an instance of +{{TransformStreamDefaultController}}, and has the ability to enqueue [=chunks=] to the [=readable +side=], or to terminate or error the stream. + +

Constructor and properties

+ +
+
stream = new {{TransformStream/constructor(transformer, writableStrategy, readableStrategy)|TransformStream}}([transformer[, writableStrategy[, readableStrategy]]]) +
+

Creates a new {{TransformStream}} wrapping the provided [=transformer=]. See + [[#transformer-api]] for more details on the transformer argument. + +

If no transformer argument is supplied, then the result will be an [=identity + transform stream=]. See this example for some cases + where that can be useful. + +

The writableStrategy and readableStrategy arguments are + the [=queuing strategy=] objects for the [=writable side|writable=] and [=readable + side|readable=] sides respectively. These are used in the construction of the {{WritableStream}} + and {{ReadableStream}} objects and can be used to add buffering to a {{TransformStream}}, in + order to smooth out variations in the speed of the transformation, or to increase the amount of + buffering in a [=pipe=]. If they are not provided, the default behavior will be the same as a + {{CountQueuingStrategy}}, with respective [=high water marks=] of 1 and 0. + +

readable = stream.{{TransformStream/readable}} +
+

Returns a {{ReadableStream}} representing the [=readable side=] of this transform stream. + +

writable = stream.{{TransformStream/writable}} +
+

Returns a {{WritableStream}} representing the [=writable side=] of this transform stream. +

- Another use of identity transform streams is to add additional buffering to a pipe. In this example we add - extra buffering between readableStream and writableStream. +
+ The TransformStream(|transformer|, + |writableStrategy|, |readableStrategy|) constructor steps are: + + 1. If |transformer| is missing, set it to null. + 1. Let |transformerDict| be |transformer|, [=converted to an IDL value=] of type {{Transformer}}. +

We cannot declare the |transformer| argument as having the {{Transformer}} type + directly, because doing so would lose the reference to the original object. We need to retain + the object so we can [=invoke=] the various methods on it. + 1. If |transformerDict|["{{Transformer/readableType}}"] [=map/exists=], throw a {{RangeError}} + exception. + 1. If |transformerDict|["{{Transformer/writableType}}"] [=map/exists=], throw a {{RangeError}} + exception. + 1. Let |readableHighWaterMark| be ? [$ExtractHighWaterMark$](|readableStrategy|, 0). + 1. Let |readableSizeAlgorithm| be ! [$ExtractSizeAlgorithm$](|readableStrategy|). + 1. Let |writableHighWaterMark| be ? [$ExtractHighWaterMark$](|writableStrategy|, 1). + 1. Let |writableSizeAlgorithm| be ! [$ExtractSizeAlgorithm$](|writableStrategy|). + 1. Let |startPromise| be [=a new promise=]. + 1. Perform ! [$InitializeTransformStream$]([=this=], |startPromise|, |writableHighWaterMark|, + |writableSizeAlgorithm|, |readableHighWaterMark|, |readableSizeAlgorithm|). + 1. Perform ? [$SetUpTransformStreamDefaultControllerFromTransformer$]([=this=], |transformer|, + |transformerDict|). + 1. Let |startPromise| be [=a new promise=]. + 1. If |transformerDict|["{{Transformer/start}}"] [=map/exists=], then [=resolve=] |startPromise| + with the result of [=invoking=] |transformerDict|["{{Transformer/start}}"] with argument list + « [=this=].\[[transformStreamController]] » and [=callback this value=] |transformer|. + 1. Otherwise, [=resolve=] |startPromise| with undefined. +

-

-    const writableStrategy = new ByteLengthQueuingStrategy({ highWaterMark: 1024 * 1024 });
+
+ The readable attribute's getter steps + are: - readableStream - .pipeThrough(new TransformStream(undefined, writableStrategy)) - .pipeTo(writableStream); -
+ 1. Return [=this=].\[[readable]].
-

Class TransformStream

+
+ The writable attribute's getter steps + are: -

Class definition

+ 1. Return [=this=].\[[writable]]. +
-
+

The {{TransformStreamDefaultController}} class

-This section is non-normative. +The {{TransformStreamDefaultController}} class has methods that allow manipulation of the +associated {{ReadableStream}} and {{WritableStream}}. When constructing a {{TransformStream}}, the +[=transformer=] object is given a corresponding {{TransformStreamDefaultController}} instance to +manipulate. -If one were to write the {{TransformStream}} class in something close to the syntax of [[!ECMASCRIPT]], it would look -like +

Interface definition

-

-  class TransformStream {
-    constructor(transformer = {}, writableStrategy = {}, readableStrategy = {})
+The Web IDL definition for the {{TransformStreamDefaultController}} class is given as follows:
 
-    get readable()
-    get writable()
-  }
-
+ +[Exposed=(Window,Worker,Worklet)] +interface TransformStreamDefaultController { + readonly attribute unrestricted double? desiredSize; -</div> + void enqueue(optional any chunk); + void error(optional any reason); + void terminate(); +}; + -

Internal slots

+

Internal slots

-Instances of {{TransformStream}} are created with the internal slots described in the following table: +Instances of {{TransformStreamDefaultController}} are created with the internal slots described in +the following table: - - - - - - - - + - + + + - + - + - +
Internal SlotDescription (non-normative)
\[[backpressure]] - Whether there was backpressure on \[[readable]] the last time it was observed -
\[[backpressureChangePromise]] - A promise which is fulfilled and replaced every time the value of \[[backpressure]] - changes -
Internal SlotDescription (non-normative)
\[[readable]] - The {{ReadableStream}} instance controlled by this object -
\[[controlledTransformStream]] + The {{TransformStream}} instance controlled
\[[transformStreamController]] - A {{TransformStreamDefaultController}} created with the ability to control \[[readable]] - and \[[writable]]; also used for the IsTransformStream brand check -
\[[flushAlgorithm]] + A promise-returning algorithm which communicates a requested close to + the [=transformer=]
\[[writable]] - The {{WritableStream}} instance controlled by this object -
\[[transformAlgorithm]] + A promise-returning algorithm, taking one argument (the [=chunk=] to + transform), which requests the [=transformer=] perform its transformation
-

new TransformStream(transformer = {}, writableStrategy = {}, -readableStrategy = {})

+

Methods and properties

+ +
+
desiredSize = controller.{{TransformStreamDefaultController/desiredSize}} +
+

Returns the [=desired size to fill a stream's internal queue|desired size to fill the + readable side's internal queue=]. It can be negative, if the queue is over-full. + +

controller.{{TransformStreamDefaultController/enqueue()|enqueue}}(chunk) +
+

Enqueues the given [=chunk=] chunk in the [=readable side=] of the controlled + transform stream. + +

controller.{{TransformStreamDefaultController/error()|error}}(e) +
+

Errors the both the [=readable side=] and the [=writable side=] of the controlled transform + stream, making all future interactions with it fail with the given error e. Any + [=chunks=] queued for transformation will be discarded. + +

controller.{{TransformStreamDefaultController/terminate()|terminate}}() +
+

Closes the [=readable side=] and errors the [=writable side=] of the controlled transform + stream. This is useful when the [=transformer=] only needs to consume a portion of the [=chunks=] + written to the [=writable side=]. +

-
- The transformer argument represents the transformer, as described in [[#transformer-api]]. - - The writableStrategy and readableStrategy arguments are the queuing strategy objects - for the writable and readable sides respectively. These are used in the construction of the {{WritableStream}} and - {{ReadableStream}} objects and can be used to add buffering to a {{TransformStream}}, in order to smooth out - variations in the speed of the transformation, or to increase the amount of buffering in a pipe. If they are - not provided, the default behavior will be the same as a {{CountQueuingStrategy}}, with respective high water - marks of 1 and 0. -
- - - 1. Let _writableSizeFunction_ be ? GetV(_writableStrategy_, `"size"`). - 1. Let _writableHighWaterMark_ be ? GetV(_writableStrategy_, `"highWaterMark"`). - 1. Let _readableSizeFunction_ be ? GetV(_readableStrategy_, `"size"`). - 1. Let _readableHighWaterMark_ be ? GetV(_readableStrategy_, `"highWaterMark"`). - 1. Let _writableType_ be ? GetV(_transformer_, `"writableType"`). - 1. If _writableType_ is not *undefined*, throw a *RangeError* exception. - 1. Let _writableSizeAlgorithm_ be ? MakeSizeAlgorithmFromSizeFunction(_writableSizeFunction_). - 1. If _writableHighWaterMark_ is *undefined*, set _writableHighWaterMark_ to *1*. - 1. Set _writableHighWaterMark_ to ? ValidateAndNormalizeHighWaterMark(_writableHighWaterMark_). - 1. Let _readableType_ be ? GetV(_transformer_, `"readableType"`). - 1. If _readableType_ is not *undefined*, throw a *RangeError* exception. - 1. Let _readableSizeAlgorithm_ be ? MakeSizeAlgorithmFromSizeFunction(_readableSizeFunction_). - 1. If _readableHighWaterMark_ is *undefined*, set _readableHighWaterMark_ to *0*. - 1. Set _readableHighWaterMark_ be ? ValidateAndNormalizeHighWaterMark(_readableHighWaterMark_). - 1. Let _startPromise_ be a new promise. - 1. Perform ! InitializeTransformStream(*this*, _startPromise_, _writableHighWaterMark_, _writableSizeAlgorithm_, - _readableHighWaterMark_, _readableSizeAlgorithm_). - 1. Perform ? SetUpTransformStreamDefaultControllerFromTransformer(*this*, _transformer_). - 1. Let _startResult_ be ? InvokeOrNoop(_transformer_, `"start"`, « *this*.[[transformStreamController]] »). - 1. Resolve _startPromise_ with _startResult_. - - -

Transformer API

+
+ The desiredSize attribute's getter steps are: -
+ 1. Let |readableController| be + [=this=].\[[controlledTransformStream]].\[[readable]].\[[readableStreamController]]. + 1. Return ! [$ReadableStreamDefaultControllerGetDesiredSize$](|readableController|). +
-This section is non-normative. +
+ The enqueue(|chunk|) method steps are: -The {{TransformStream()}} constructor accepts as its first argument a JavaScript object representing the -transformer. Such objects can contain any of the following methods: + 1. Perform ? [$TransformStreamDefaultControllerEnqueue$]([=this=], |chunk|). +
-
-
start(controller)
-
-

A function that is called immediately during creation of the {{TransformStream}}.

+
+ The error(|e|) method steps are: -

Typically this is used to enqueue prefix chunks, using - {{TransformStreamDefaultController/enqueue()|controller.enqueue()}}. Those chunks will be read from the readable - side but don't depend on any writes to the writable side.

+ 1. Perform ? [$TransformStreamDefaultControllerError$]([=this=], |e|). +
-

If this initial process is asynchronous, for example because it takes some effort to acquire the prefix - chunks, the function can return a promise to signal success or failure; a rejected promise will error the stream. - Any thrown exceptions will be re-thrown by the {{TransformStream()}} constructor.

-
+
+ The terminate() method steps are: -
transform(chunk, controller)
-
-

A function called when a new chunk originally written to the writable side is ready to be - transformed. The stream implementation guarantees that this function will be called only after previous transforms - have succeeded, and never before {{underlying sink/start()}} has completed or after {{transformer/flush()}} has been - called.

- -

This function performs the actual transformation work of the transform stream. It can enqueue the results using - {{TransformStreamDefaultController/enqueue()|controller.enqueue()}}. This permits a single chunk written to the - writable side to result in zero or multiple chunks on the readable side, depending on how many times - {{TransformStreamDefaultController/enqueue()|controller.enqueue()}} is called. [[#example-ts-lipfuzz]] demonstrates - this by sometimes enqueuing zero chunks.

- -

If the process of transforming is asynchronous, this function can return a promise to signal success or failure - of the transformation. A rejected promise will error both the readable and writable sides of the transform - stream.

- -

If no {{transformer/transform()}} is supplied, the identity transform is used, which enqueues chunks unchanged - from the writable side to the readable side.

-
- -
flush(controller)
-
-

A function called after all chunks written to the writable side have been transformed by - successfully passing through {{transformer/transform()}}, and the writable side is about to be closed.

+ 1. Perform ? [$TransformStreamDefaultControllerTerminate$]([=this=]). +
-

Typically this is used to enqueue suffix chunks to the readable side, before that too becomes closed. An - example can be seen in [[#example-ts-lipfuzz]].

+

Abstract operations

+ +

Working with transform streams

+ +The following abstract operations operate on {{TransformStream}} instances at a higher level. Some +are even meant to be generally useful by other specifications. + +
+ CreateTransformStream(|startAlgorithm|, |transformAlgorithm|, |flushAlgorithm|[, + |writableHighWaterMark|[, |writableSizeAlgorithm|[, |readableHighWaterMark|[, + |readableSizeAlgorithm|]]]]) is meant to be called from other specifications that wish to + create {{TransformStream}} instances. The |transformAlgorithm| and |flushAlgorithm| algorithms + must return promises; if supplied, |writableHighWaterMark| and |readableHighWaterMark| must be + non-negative, non-NaN numbers; and if supplied, |writableSizeAlgorithm| and + |readableSizeAlgorithm| must be algorithms accepting [=chunk=] objects and returning numbers. + + It performs the following steps: + + 1. If |writableHighWaterMark| was not passed, set it to 1. + 1. If |writableSizeAlgorithm| was not passed, set it to an algorithm that returns 1. + 1. If |readableHighWaterMark| was not passed, set it to 0. + 1. If |readableSizeAlgorithm| was not passed, set it to an algorithm that returns 1. + 1. Assert: ! [$IsNonNegativeNumber$](|writableHighWaterMark|) is true. + 1. Assert: ! [$IsNonNegativeNumber$](|readableHighWaterMark|) is true. + 1. Let |stream| be a [=new=] {{TransformStream}}. + 1. Let |startPromise| be [=a new promise=]. + 1. Perform ! [$InitializeTransformStream$](|stream|, |startPromise|, |writableHighWaterMark|, + |writableSizeAlgorithm|, |readableHighWaterMark|, |readableSizeAlgorithm|). + 1. Let |controller| be a [=new=] {{TransformStreamDefaultController}}. + 1. Perform ! [$SetUpTransformStreamDefaultController$](|stream|, |controller|, + |transformAlgorithm|, |flushAlgorithm|). + 1. Let |startResult| be the result of performing |startAlgorithm|. (This may throw an exception.) + 1. [=Resolve=] |startPromise| with |startResult|. + 1. Return |stream|. + +

This abstract operation will throw an exception if and only if the supplied + |startAlgorithm| throws. +

-

If the flushing process is asynchronous, the function can return a promise to signal success or failure; the - result will be communicated to the caller of {{WritableStreamDefaultWriter/write()|stream.writable.write()}}. - Additionally, a rejected promise will error both the readable and writable sides of the stream. Throwing an - exception is treated the same as returning a rejected promise.

+
+ InitializeTransformStream(|stream|, |startPromise|, + |writableHighWaterMark|, |writableSizeAlgorithm|, |readableHighWaterMark|, + |readableSizeAlgorithm|) performs the following steps: + + 1. Let |startAlgorithm| be an algorithm that returns |startPromise|. + 1. Let |writeAlgorithm| be the following steps, taking a |chunk| argument: + 1. Return ! [$TransformStreamDefaultSinkWriteAlgorithm$](|stream|, |chunk|). + 1. Let |abortAlgorithm| be the following steps, taking a |reason| argument: + 1. Return ! [$TransformStreamDefaultSinkAbortAlgorithm$](|stream|, |reason|). + 1. Let |closeAlgorithm| be the following steps: + 1. Return ! [$TransformStreamDefaultSinkCloseAlgorithm$](|stream|). + 1. Set |stream|.\[[writable]] to ! [$CreateWritableStream$](|startAlgorithm|, |writeAlgorithm|, + |closeAlgorithm|, |abortAlgorithm|, |writableHighWaterMark|, |writableSizeAlgorithm|). + 1. Let |pullAlgorithm| be the following steps: + 1. Return ! [$TransformStreamDefaultSourcePullAlgorithm$](|stream|). + 1. Let |cancelAlgorithm| be the following steps, taking a |reason| argument: + 1. Perform ! [$TransformStreamErrorWritableAndUnblockWrite$](|stream|, |reason|). + 1. Return [=a promise resolved with=] undefined. + 1. Set |stream|.\[[readable]] to ! [$CreateReadableStream$](|startAlgorithm|, |pullAlgorithm|, + |cancelAlgorithm|, |readableHighWaterMark|, |readableSizeAlgorithm|). + 1. Set |stream|.\[[backpressure]] and |stream|.\[[backpressureChangePromise]] to undefined. +

The \[[backpressure]] slot is set to undefined so that it can be initialized by + [$TransformStreamSetBackpressure$]. Alternatively, implementations can use a strictly boolean + value for \[[backpressure]] and change the way it is initialized. This will not be visible to + user code so long as the initialization is correctly completed before the transformer's + {{Transformer/start|start()}} method is called. + 1. Perform ! [$TransformStreamSetBackpressure$](|stream|, true). + 1. Set |stream|.\[[transformStreamController]] to undefined. +

-

(Note that there is no need to call {{TransformStreamDefaultController/terminate()|controller.terminate()}} - inside {{transformer/flush()}}; the stream is already in the process of successfully closing down, and terminating - it would be counterproductive.)

- -
+
+ TransformStreamError(|stream|, |e|) performs the following steps: -The controller object passed to {{transformer/start()}}, {{transformer/transform()}}, and -{{transformer/flush()}} is an instance of {{TransformStreamDefaultController}}, and has the ability to enqueue -chunks to the readable side, or to terminate or error the stream. + 1. Perform ! + [$ReadableStreamDefaultControllerError$](|stream|.\[[readable]].\[[readableStreamController]], + |e|). + 1. Perform ! [$TransformStreamErrorWritableAndUnblockWrite$](|stream|, |e|). +

This operation works correctly when one or both sides are already errored. As a + result, calling algorithms do not need to check stream states when responding to an error + condition.

-

Properties of the {{TransformStream}} prototype

+
+ TransformStreamErrorWritableAndUnblockWrite(|stream|, + |e|) performs the following steps: + + 1. Perform ! + [$TransformStreamDefaultControllerClearAlgorithms$](|stream|.\[[transformStreamController]]). + 1. Perform ! + [$WritableStreamDefaultControllerErrorIfNeeded$](|stream|.\[[writable]].\[[writableStreamController]], + |e|). + 1. If |stream|.\[[backpressure]] is true, perform ! [$TransformStreamSetBackpressure$](|stream|, + false). + +

The [$TransformStreamDefaultSinkWriteAlgorithm$] abstract operation could be + waiting for the promise stored in the \[[backpressureChangePromise]] slot to resolve. The call to + [$TransformStreamSetBackpressure$] ensures that the promise always resolves. +

-
get readable
+
+ TransformStreamSetBackpressure(|stream|, + |backpressure|) performs the following steps: -
- The readable getter gives access to the readable side of the transform stream. + 1. Assert: |stream|.\[[backpressure]] is not |backpressure|. + 1. If |stream|.\[[backpressureChangePromise]] is not undefined, [=resolve=] + stream.\[[backpressureChangePromise]] with undefined. + 1. Set |stream|.\[[backpressureChangePromise]] to [=a new promise=]. + 1. Set |stream|.\[[backpressure]] to |backpressure|.
- - 1. If ! IsTransformStream(*this*) is *false*, throw a *TypeError* exception. - 1. Return *this*.[[readable]]. - - -
get writable
+

Default controllers

-
- The writable getter gives access to the writable side of the transform stream. -
- - - 1. If ! IsTransformStream(*this*) is *false*, throw a *TypeError* exception. - 1. Return *this*.[[writable]]. - - -

General transform stream abstract operations

- -

CreateTransformStream ( -startAlgorithm, transformAlgorithm, flushAlgorithm -[, writableHighWaterMark [, writableSizeAlgorithm [, readableHighWaterMark [, -readableSizeAlgorithm ] ] ] ] )

- -This abstract operation is meant to be called from other specifications that wish to create {{TransformStream}} -instances. The transformAlgorithm and flushAlgorithm algorithms must return promises; if supplied, -writableHighWaterMark and readableHighWaterMark must be non-negative, non-NaN numbers; and if -supplied, writableSizeAlgorithm and readableSizeAlgorithm must be algorithms accepting -chunk objects and returning numbers. - -

CreateTransformStream throws an exception if and only if the supplied startAlgorithm -throws.

- - - 1. If _writableHighWaterMark_ was not passed, set it to *1*. - 1. If _writableSizeAlgorithm_ was not passed, set it to an algorithm that returns *1*. - 1. If _readableHighWaterMark_ was not passed, set it to *0*. - 1. If _readableSizeAlgorithm_ was not passed, set it to an algorithm that returns *1*. - 1. Assert: ! IsNonNegativeNumber(_writableHighWaterMark_) is *true*. - 1. Assert: ! IsNonNegativeNumber(_readableHighWaterMark_) is *true*. - 1. Let _stream_ be ObjectCreate(the original value of `TransformStream`'s `prototype` property). - 1. Let _startPromise_ be a new promise. - 1. Perform ! InitializeTransformStream(_stream_, _startPromise_, _writableHighWaterMark_, _writableSizeAlgorithm_, - _readableHighWaterMark_, _readableSizeAlgorithm_). - 1. Let _controller_ be ObjectCreate(the original value of `TransformStreamDefaultController`'s `prototype` - property). - 1. Perform ! SetUpTransformStreamDefaultController(_stream_, _controller_, _transformAlgorithm_, _flushAlgorithm_). - 1. Let _startResult_ be the result of performing _startAlgorithm_. (This may throw an exception.) - 1. Resolve _startPromise_ with _startResult_. - 1. Return _stream_. - - -

InitializeTransformStream ( -stream, startPromise, writableHighWaterMark, writableSizeAlgorithm, -readableHighWaterMark, readableSizeAlgorithm )

- - - 1. Let _startAlgorithm_ be an algorithm that returns _startPromise_. - 1. Let _writeAlgorithm_ be the following steps, taking a _chunk_ argument: - 1. Return ! TransformStreamDefaultSinkWriteAlgorithm(_stream_, _chunk_). - 1. Let _abortAlgorithm_ be the following steps, taking a _reason_ argument: - 1. Return ! TransformStreamDefaultSinkAbortAlgorithm(_stream_, _reason_). - 1. Let _closeAlgorithm_ be the following steps: - 1. Return ! TransformStreamDefaultSinkCloseAlgorithm(_stream_). - 1. Set _stream_.[[writable]] to ! CreateWritableStream(_startAlgorithm_, _writeAlgorithm_, _closeAlgorithm_, - _abortAlgorithm_, _writableHighWaterMark_, _writableSizeAlgorithm_). - 1. Let _pullAlgorithm_ be the following steps: - 1. Return ! TransformStreamDefaultSourcePullAlgorithm(_stream_). - 1. Let _cancelAlgorithm_ be the following steps, taking a _reason_ argument: - 1. Perform ! TransformStreamErrorWritableAndUnblockWrite(_stream_, _reason_). - 1. Return a promise resolved with *undefined*. - 1. Set _stream_.[[readable]] to ! CreateReadableStream(_startAlgorithm_, _pullAlgorithm_, _cancelAlgorithm_, - _readableHighWaterMark_, _readableSizeAlgorithm_). - 1. Set _stream_.[[backpressure]] and _stream_.[[backpressureChangePromise]] to *undefined*. -

The [[backpressure]] slot is set to *undefined* so that it can be initialized by - TransformStreamSetBackpressure. Alternatively, implementations can use a strictly boolean value for - [[backpressure]] and change the way it is initialized. This will not be visible to user code so long as the - initialization is correctly completed before _transformer_'s {{transformer/start()}} method is called.

- 1. Perform ! TransformStreamSetBackpressure(_stream_, *true*). - 1. Set _stream_.[[transformStreamController]] to *undefined*. -
- -

IsTransformStream ( x )

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

TransformStreamError ( stream, -e )

- - - 1. Perform ! ReadableStreamDefaultControllerError(_stream_.[[readable]].[[readableStreamController]], _e_). - 1. Perform ! TransformStreamErrorWritableAndUnblockWrite(_stream_, _e_). - - -

This operation works correctly when one or both sides are already errored. As a result, calling -algorithms do not need to check stream states when responding to an error condition.

- -

TransformStreamErrorWritableAndUnblockWrite ( stream, e )

- - - 1. Perform ! TransformStreamDefaultControllerClearAlgorithms(_stream_.[[transformStreamController]]). - 1. Perform ! WritableStreamDefaultControllerErrorIfNeeded(_stream_.[[writable]].[[writableStreamController]], _e_). - 1. If _stream_.[[backpressure]] is *true*, perform ! TransformStreamSetBackpressure(_stream_, *false*). - - -

The TransformStreamDefaultSinkWriteAlgorithm abstract operation could be waiting for the promise -stored in the \[[backpressureChangePromise]] slot to resolve. This call to TransformStreamSetBackpressure ensures that -the promise always resolves.

- -

TransformStreamSetBackpressure -( stream, backpressure )

- - - 1. Assert: _stream_.[[backpressure]] is not _backpressure_. - 1. If _stream_.[[backpressureChangePromise]] is not *undefined*, resolve - stream.[[backpressureChangePromise]] with *undefined*. - 1. Set _stream_.[[backpressureChangePromise]] to a new promise. - 1. Set _stream_.[[backpressure]] to _backpressure_. - - -

Class -TransformStreamDefaultController

- -The {{TransformStreamDefaultController}} class has methods that allow manipulation of the associated {{ReadableStream}} -and {{WritableStream}}. When constructing a {{TransformStream}}, the transformer object is given a corresponding -{{TransformStreamDefaultController}} instance to manipulate. - -

Class definition

+The following abstract operations support the implementaiton of the +{{TransformStreamDefaultController}} class. -
+
+ SetUpTransformStreamDefaultController(|stream|, + |controller|, |transformAlgorithm|, |flushAlgorithm|) performs the following steps: -This section is non-normative. + 1. Assert: |stream| [=implements=] {{TransformStream}}. + 1. Assert: |stream|.\[[transformStreamController]] is undefined. + 1. Set |controller|.\[[controlledTransformStream]] to |stream|. + 1. Set |stream|.\[[transformStreamController]] to |controller|. + 1. Set |controller|.\[[transformAlgorithm]] to |transformAlgorithm|. + 1. Set |controller|.\[[flushAlgorithm]] to |flushAlgorithm|. +
-If one were to write the {{TransformStreamDefaultController}} class in something close to the syntax of [[!ECMASCRIPT]], -it would look like +
+ SetUpTransformStreamDefaultControllerFromTransformer(|stream|, + |transformer|, |transformerDict|) performs the following steps: + + 1. Let |controller| be a [=new=] {{TransformStreamDefaultController}}. + 1. Let |transformAlgorithm| be the following steps, taking a |chunk| argument: + 1. Let |result| be [$TransformStreamDefaultControllerEnqueue$](|controller|, |chunk|). + 1. If |result| is an abrupt completion, return [=a promise rejected with=] |result|.\[[Value]]. + 1. Otherwise, return [=a promise resolved with=] undefined. + 1. Let |flushAlgorithm| be an algorithm which returns [=a promise resolved with=] undefined. + 1. If |transformerDict|["{{Transformer/transform}}"] [=map/exists=], set |transformAlgorithm| to an + algorithm which takes an argument |chunk| and returns the result of [=invoking=] + |transformerDict|["{{Transformer/transform}}"] with argument list « |chunk|, + |controller| ») and [=callback this value=] |transformer|. + 1. If |transformerDict|["{{Transformer/flush}}"] [=map/exists=], set |flushAlgorithm| to an + algorithm which returns the result of [=invoking=] |transformerDict|["{{Transformer/flush}}"] + with argument list « |controller| » and [=callback this value=] |transformer|. + 1. Perform ! [$SetUpTransformStreamDefaultController$](|stream|, |controller|, + |transformAlgorithm|, |flushAlgorithm|). +
-

-  class TransformStreamDefaultController {
-    constructor() // always throws
+
+ TransformStreamDefaultControllerClearAlgorithms(|controller|) + is called once the stream is closed or errored and the algorithms will not be executed any more. + By removing the algorithm references it permits the [=transformer=] object to be garbage collected + even if the {{TransformStream}} itself is still referenced. - get desiredSize() +

This is observable using weak + references. See tc39/proposal-weakrefs#31 for more + detail. - enqueue(chunk) - error(reason) - terminate() - } -

+ It performs the following steps: + 1. Set |controller|.\[[transformAlgorithm]] to undefined. + 1. Set |controller|.\[[flushAlgorithm]] to undefined.
-

Internal slots

+
+ TransformStreamDefaultControllerEnqueue(|controller|, |chunk|) is meant to be called + by other specifications that wish to enqueue [=chunks=] in the [=readable side=], in the same way + a developer would enqueue chunks using the stream's associated controller object. Specifications + should not do this to streams or controllers they did not create. + + It performs the following steps: + + 1. Let |stream| be |controller|.\[[controlledTransformStream]]. + 1. Let |readableController| be |stream|.\[[readable]].\[[readableStreamController]]. + 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$](|readableController|) is false, throw + a {{TypeError}} exception. + 1. Let |enqueueResult| be [$ReadableStreamDefaultControllerEnqueue$](|readableController|, + |chunk|). + 1. If |enqueueResult| is an abrupt completion, + 1. Perform ! [$TransformStreamErrorWritableAndUnblockWrite$](|stream|, + |enqueueResult|.\[[Value]]). + 1. Throw |stream|.\[[readable]].\[[storedError]]. + 1. Let |backpressure| be ! + [$ReadableStreamDefaultControllerHasBackpressure$](|readableController|). + 1. If |backpressure| is not |stream|.\[[backpressure]], + 1. Assert: |backpressure| is true. + 1. Perform ! [$TransformStreamSetBackpressure$](|stream|, true). +
+
+ TransformStreamDefaultControllerError(|controller|, |e|) is meant to be called by + other specifications that wish to move the transform stream to an errored state, in the same way a + developer would error the stream using the stream's associated controller object. Specifications + should not do this to streams or controllers they did not create. -Instances of {{TransformStreamDefaultController}} are created with the internal slots described in the following table: + It performs the following steps: - - - - - - - - - - - - -
Internal SlotDescription (non-normative)
\[[controlledTransformStream]] - The {{TransformStream}} instance controlled; also used for the - IsTransformStreamDefaultController brand check -
\[[flushAlgorithm]] - A promise-returning algorithm which communicates a requested close to the - transformer -
\[[transformAlgorithm]] - A promise-returning algorithm, taking one argument (the chunk to transform), which - requests the transformer perform its transformation -
+ 1. Perform ! [$TransformStreamError$](|controller|.\[[controlledTransformStream]], |e|). +
-

new TransformStreamDefaultController()

+
+ TransformStreamDefaultControllerPerformTransform(|controller|, |chunk|) + performs the following steps: + + 1. Let |transformPromise| be the result of performing + |controller|.\[[transformAlgorithm]], passing |chunk|. + 1. Return the result of [=reacting=] to |transformPromise| with the following + rejection steps given the argument |r|: + 1. Perform ! [$TransformStreamError$](|controller|.\[[controlledTransformStream]], |r|). + 1. Throw |r|. +
-
- The TransformStreamDefaultController constructor cannot be used directly; - {{TransformStreamDefaultController}} instances are created automatically during {{TransformStream}} construction. +
+ TransformStreamDefaultControllerTerminate(|controller|) is meant to be called by + other specifications that wish to terminate the transform stream, in the same way a + developer-created stream would be terminated by its associated controller object. Specifications + should not do this to streams or controllers they did not create. + + It performs the following steps: + + 1. Let |stream| be |controller|.\[[controlledTransformStream]]. + 1. Let |readableController| be |stream|.\[[readable]].\[[readableStreamController]]. + 1. Perform ! [$ReadableStreamDefaultControllerClose$](|readableController|). + 1. Let |error| be a {{TypeError}} exception indicating that the stream has been terminated. + 1. Perform ! [$TransformStreamErrorWritableAndUnblockWrite$](|stream|, |error|).
- - 1. Throw a *TypeError* exception. - +

Default sinks

+ +The following abstract operations are used to implement the [=underlying sink=] for the [=writable +side=] of [=transform streams=]. + +
+ TransformStreamDefaultSinkWriteAlgorithm(|stream|, + |chunk|) performs the following steps: + + 1. Assert: |stream|.\[[writable]].\[[state]] is "`writable`". + 1. Let |controller| be |stream|.\[[transformStreamController]]. + 1. If |stream|.\[[backpressure]] is true, + 1. Let |backpressureChangePromise| be |stream|.\[[backpressureChangePromise]]. + 1. Assert: |backpressureChangePromise| is not undefined. + 1. Return the result of [=reacting=] to |backpressureChangePromise| with the following fulfillment + steps: + 1. Let |writable| be |stream|.\[[writable]]. + 1. Let |state| be |writable|.\[[state]]. + 1. If |state| is "`erroring`", throw |writable|.\[[storedError]]. + 1. Assert: |state| is "`writable`". + 1. Return ! [$TransformStreamDefaultControllerPerformTransform$](|controller|, |chunk|). + 1. Return ! [$TransformStreamDefaultControllerPerformTransform$](|controller|, |chunk|). +
-

Properties of the {{TransformStreamDefaultController}} prototype

+
+ TransformStreamDefaultSinkAbortAlgorithm(|stream|, + |reason|) performs the following steps: -
get -desiredSize
+ 1. Perform ! [$TransformStreamError$](|stream|, |reason|). + 1. Return [=a promise resolved with=] undefined. +
-
- The desiredSize getter returns the desired size - to fill the readable side's internal queue. It can be negative, if the queue is over-full. +
+ TransformStreamDefaultSinkCloseAlgorithm(|stream|) + performs the following steps: + + 1. Let |readable| be |stream|.\[[readable]]. + 1. Let |controller| be |stream|.\[[transformStreamController]]. + 1. Let |flushPromise| be the result of performing |controller|.\[[flushAlgorithm]]. + 1. Perform ! [$TransformStreamDefaultControllerClearAlgorithms$](|controller|). + 1. Return the result of [=reacting=] to |flushPromise|: + 1. If |flushPromise| was fulfilled, then: + 1. If |readable|.\[[state]] is "`errored`", throw |readable|.\[[storedError]]. + 1. Let |readableController| be |readable|.\[[readableStreamController]]. + 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$](|readableController|) is true, + perform ! [$ReadableStreamDefaultControllerClose$](|readableController|). + 1. If |flushPromise| was rejected with reason |r|, then: + 1. Perform ! [$TransformStreamError$](|stream|, |r|). + 1. Throw |readable|.\[[storedError]].
- - 1. If ! IsTransformStreamDefaultController(*this*) is *false*, throw a *TypeError* exception. - 1. Let _readableController_ be *this*.[[controlledTransformStream]].[[readable]].[[readableStreamController]]. - 1. Return ! ReadableStreamDefaultControllerGetDesiredSize(_readableController_). - +

Default sources

-
enqueue(chunk)
+The following abstract operation is used to implement the [=underlying source=] for the [=readable +side=] of [=transform streams=]. -
- The enqueue method will enqueue a given chunk in the readable side. +
+ TransformStreamDefaultSourcePullAlgorithm(|stream|) + performs the following steps: + + 1. Assert: |stream|.\[[backpressure]] is true. + 1. Assert: |stream|.\[[backpressureChangePromise]] is not undefined. + 1. Perform ! [$TransformStreamSetBackpressure$](|stream|, false). + 1. Return |stream|.\[[backpressureChangePromise]].
- - 1. If ! IsTransformStreamDefaultController(*this*) is *false*, throw a *TypeError* exception. - 1. Perform ? TransformStreamDefaultControllerEnqueue(*this*, _chunk_). - +

Queuing strategies

-
error(reason)
+

The queuing strategy API

-
- The error method will error both the readable side and the writable side of the controlled - transform stream, making all future interactions fail with the given reason. Any chunks - queued for transformation will be discarded. -
+The {{ReadableStream()}}, {{WritableStream()}}, and {{TransformStream()}} constructors all accept +at least one argument representing an appropriate [=queuing strategy=] for the stream being +created. Such objects contain the following properties: - - 1. If ! IsTransformStreamDefaultController(*this*) is *false*, throw a *TypeError* exception. - 1. Perform ! TransformStreamDefaultControllerError(*this*, _reason_). - + +dictionary QueuingStrategy { + unrestricted double highWaterMark; + QueuingStrategySize size; +}; -<h5 id="ts-default-controller-terminate" method for="TransformStreamDefaultController">terminate()</h5> +callback QueuingStrategySize = unrestricted double (optional any chunk); + -
- The terminate method will close the readable side and error the writable side of the - controlled transform stream. This is useful when the transformer only needs to consume a portion of the - chunks written to the writable side. -
- - - 1. If ! IsTransformStreamDefaultController(*this*) is *false*, throw a *TypeError* exception. - 1. Perform ! TransformStreamDefaultControllerTerminate(*this*). - - -

Transform stream default controller abstract operations

- -

IsTransformStreamDefaultController ( x )

- - - 1. If Type(_x_) is not Object, return *false*. - 1. If _x_ does not have an [[controlledTransformStream]] internal slot, return *false*. - 1. Return *true*. - - -

SetUpTransformStreamDefaultController ( stream, controller, transformAlgorithm, -flushAlgorithm )

- - - 1. Assert: ! IsTransformStream(_stream_) is *true*. - 1. Assert: _stream_.[[transformStreamController]] is *undefined*. - 1. Set _controller_.[[controlledTransformStream]] to _stream_. - 1. Set _stream_.[[transformStreamController]] to _controller_. - 1. Set _controller_.[[transformAlgorithm]] to _transformAlgorithm_. - 1. Set _controller_.[[flushAlgorithm]] to _flushAlgorithm_. - - -

SetUpTransformStreamDefaultControllerFromTransformer ( stream, transformer )

- - - 1. Assert: _transformer_ is not *undefined*. - 1. Let _controller_ be ObjectCreate(the original value of `TransformStreamDefaultController`'s `prototype` - property). - 1. Let _transformAlgorithm_ be the following steps, taking a _chunk_ argument: - 1. Let _result_ be TransformStreamDefaultControllerEnqueue(_controller_, _chunk_). - 1. If _result_ is an abrupt completion, return a promise rejected with _result_.[[Value]]. - 1. Otherwise, return a promise resolved with *undefined*. - 1. Let _transformMethod_ be ? GetV(_transformer_, `"transform"`). - 1. If _transformMethod_ is not *undefined*, - 1. If ! IsCallable(_transformMethod_) is *false*, throw a *TypeError* exception. - 1. Set _transformAlgorithm_ to the following steps, taking a _chunk_ argument: - 1. Return ! PromiseCall(_transformMethod_, _transformer_, « _chunk_, _controller_ »). - 1. Let _flushAlgorithm_ be ? CreateAlgorithmFromUnderlyingMethod(_transformer_, `"flush"`, *0*, « controller »). - 1. Perform ! SetUpTransformStreamDefaultController(_stream_, _controller_, _transformAlgorithm_, _flushAlgorithm_). - - -

TransformStreamDefaultControllerClearAlgorithms ( controller )

- -This abstract operation is called once the stream is closed or errored and the algorithms will not be executed any more. -By removing the algorithm references it permits the transformer object to be garbage collected even if the -{{TransformStream}} itself is still referenced. - -

The results of this algorithm are not currently observable, but could become so if JavaScript eventually -adds weak references. But even without that factor, -implementations will likely want to include similar steps.

- - - 1. Set _controller_.[[transformAlgorithm]] to *undefined*. - 1. Set _controller_.[[flushAlgorithm]] to *undefined*. - - -

TransformStreamDefaultControllerEnqueue ( controller, chunk )

- -This abstract operation can be called by other specifications that wish to enqueue chunks in the readable -side, in the same way a developer would enqueue chunks using the stream's associated controller object. -Specifications should not do this to streams they did not create. - - - 1. Let _stream_ be _controller_.[[controlledTransformStream]]. - 1. Let _readableController_ be _stream_.[[readable]].[[readableStreamController]]. - 1. If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(_readableController_) is *false*, throw a *TypeError* - exception. - 1. Let _enqueueResult_ be ReadableStreamDefaultControllerEnqueue(_readableController_, _chunk_). - 1. If _enqueueResult_ is an abrupt completion, - 1. Perform ! TransformStreamErrorWritableAndUnblockWrite(_stream_, _enqueueResult_.[[Value]]). - 1. Throw _stream_.[[readable]].[[storedError]]. - 1. Let _backpressure_ be ! ReadableStreamDefaultControllerHasBackpressure(_readableController_). - 1. If _backpressure_ is not _stream_.[[backpressure]], - 1. Assert: _backpressure_ is *true*. - 1. Perform ! TransformStreamSetBackpressure(_stream_, *true*). - - -

TransformStreamDefaultControllerError ( controller, e )

- -This abstract operation can be called by other specifications that wish to move a transform stream to an errored state, -in the same way a developer would error a stream using its associated controller object. Specifications should -not do this to streams they did not create. - - - 1. Perform ! TransformStreamError(_controller_.[[controlledTransformStream]], _e_). - - -

TransformStreamDefaultControllerPerformTransform ( controller, chunk )

- - - 1. Let _transformPromise_ be the result of performing _controller_.[[transformAlgorithm]], passing _chunk_. - 1. Return the result of reacting to _transformPromise_ with the following rejection steps given the - argument _r_: - 1. Perform ! TransformStreamError(_controller_.[[controlledTransformStream]], _r_). - 1. Throw _r_. - - -

TransformStreamDefaultControllerTerminate ( controller )

- -This abstract operation can be called by other specifications that wish to terminate a transform stream, in the same way -a developer-created stream would be closed by its associated controller object. Specifications should not do -this to streams they did not create. - - - 1. Let _stream_ be _controller_.[[controlledTransformStream]]. - 1. Let _readableController_ be _stream_.[[readable]].[[readableStreamController]]. - 1. Perform ! ReadableStreamDefaultControllerClose(_readableController_). - 1. Let _error_ be a *TypeError* exception indicating that the stream has been terminated. - 1. Perform ! TransformStreamErrorWritableAndUnblockWrite(_stream_, _error_). - - -

Transform stream default sink abstract operations

- -

TransformStreamDefaultSinkWriteAlgorithm ( stream, chunk )

- - - 1. Assert: _stream_.[[writable]].[[state]] is `"writable"`. - 1. Let _controller_ be _stream_.[[transformStreamController]]. - 1. If _stream_.[[backpressure]] is *true*, - 1. Let _backpressureChangePromise_ be _stream_.[[backpressureChangePromise]]. - 1. Assert: _backpressureChangePromise_ is not *undefined*. - 1. Return the result of reacting to _backpressureChangePromise_ with the following fulfillment steps: - 1. Let _writable_ be _stream_.[[writable]]. - 1. Let _state_ be _writable_.[[state]]. - 1. If _state_ is `"erroring"`, throw _writable_.[[storedError]]. - 1. Assert: _state_ is `"writable"`. - 1. Return ! TransformStreamDefaultControllerPerformTransform(_controller_, _chunk_). - 1. Return ! TransformStreamDefaultControllerPerformTransform(_controller_, _chunk_). - - -

TransformStreamDefaultSinkAbortAlgorithm ( stream, reason )

- - - 1. Perform ! TransformStreamError(_stream_, _reason_). - 1. Return a promise resolved with *undefined*. - - -

TransformStreamDefaultSinkCloseAlgorithm( stream )

- - - 1. Let _readable_ be _stream_.[[readable]]. - 1. Let _controller_ be _stream_.[[transformStreamController]]. - 1. Let _flushPromise_ be the result of performing _controller_.[[flushAlgorithm]]. - 1. Perform ! TransformStreamDefaultControllerClearAlgorithms(_controller_). - 1. Return the result of reacting to _flushPromise_: - 1. If _flushPromise_ was fulfilled, then: - 1. If _readable_.[[state]] is `"errored"`, throw _readable_.[[storedError]]. - 1. Let _readableController_ be _readable_.[[readableStreamController]]. - 1. If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(_readableController_) is *true*, perform ! - ReadableStreamDefaultControllerClose(_readableController_). - 1. If _flushPromise_ was rejected with reason _r_, then: - 1. Perform ! TransformStreamError(_stream_, _r_). - 1. Throw _readable_.[[storedError]]. - - -

Transform stream default source abstract operations

- -

TransformStreamDefaultSourcePullAlgorithm( stream -)

- - - 1. Assert: _stream_.[[backpressure]] is *true*. - 1. Assert: _stream_.[[backpressureChangePromise]] is not *undefined*. - 1. Perform ! TransformStreamSetBackpressure(_stream_, *false*). - 1. Return _stream_.[[backpressureChangePromise]]. - - -

Other stream APIs and operations

- -

Queuing strategies

- -

The queuing strategy API

+
+
highWaterMark
+
+

A non-negative number indicating the [=high water mark=] of the stream using this queuing + strategy. + +

size(chunk) (non-byte streams only)
+
+

A function that computes and returns the finite non-negative size of the given [=chunk=] + value. + +

The result is used to determine [=backpressure=], manifesting via the appropriate + desiredSize + property: either {{ReadableStreamDefaultController/desiredSize|defaultController.desiredSize}}, + {{ReadableByteStreamController/desiredSize|byteController.desiredSize}}, or + {{WritableStreamDefaultWriter/desiredSize|writer.desiredSize}}, depending on where the queuing + strategy is being used. For readable streams, it also governs when the [=underlying source=]'s + {{UnderlyingSource/pull|pull()}} method is called. + +

This function has to be idempotent and not cause side effects; very strange results can occur + otherwise. + +

For [=readable byte streams=], this function is not used, as chunks are always measured in + bytes. +

-
+Any object with these properties can be used when a queuing strategy object is expected. However, +we provide two built-in queuing strategy classes that provide a common vocabulary for certain +cases: {{ByteLengthQueuingStrategy}} and {{CountQueuingStrategy}}. They both make use of the +following Web IDL fragment for their constructors: -This section is non-normative. + +dictionary QueuingStrategyInit { + required unrestricted double highWaterMark; +}; + -The {{ReadableStream()}}, {{WritableStream()}}, and {{TransformStream()}} constructors all accept at least one argument -representing an appropriate queuing strategy for the stream being created. Such objects contain the following -properties: +

The {{ByteLengthQueuingStrategy}} class

-
-
size(chunk) (non-byte streams only)
-
-

A function that computes and returns the finite non-negative size of the given chunk value.

+A common [=queuing strategy=] when dealing with bytes is to wait until the accumulated +byteLength properties of the incoming [=chunks=] reaches a specified high-water mark. +As such, this is provided as a built-in [=queuing strategy=] that can be used when constructing +streams. -

The result is used to determine backpressure, manifesting via the appropriate desiredSize - property: either - {{ReadableStreamDefaultController/desiredSize|defaultController.desiredSize}}, - {{ReadableByteStreamController/desiredSize|byteController.desiredSize}}, or - {{WritableStreamDefaultWriter/desiredSize|writer.desiredSize}}, depending on where the queuing strategy is being - used. For readable streams, it also governs when the underlying source's {{underlying source/pull()}} method - is called.

+
+ When creating a [=readable stream=] or [=writable stream=], you can supply a byte-length queuing + strategy directly: + + + const stream = new ReadableStream( + { ... }, + new ByteLengthQueuingStrategy({ highWaterMark: 16 * 1024 }) + ); + + + In this case, 16 KiB worth of [=chunks=] can be enqueued by the readable stream's [=underlying + source=] before the readable stream implementation starts sending [=backpressure=] signals to the + underlying source. + + + const stream = new WritableStream( + { ... }, + new ByteLengthQueuingStrategy({ highWaterMark: 32 * 1024 }) + ); + + + In this case, 32 KiB worth of [=chunks=] can be accumulated in the writable stream's internal + queue, waiting for previous writes to the [=underlying sink=] to finish, before the writable + stream starts sending [=backpressure=] signals to any [=producers=]. +
-

This function has to be idempotent and not cause side effects; very strange results can occur otherwise.

+

It is not necessary to use {{ByteLengthQueuingStrategy}} with [=readable byte +streams=], as they always measure chunks in bytes. Attempting to construct a byte stream with a +{{ByteLengthQueuingStrategy}} will fail. -

For readable byte streams, this function is not used, as chunks are always measured in bytes.

-
+

Interface definition

-
highWaterMark
-
-

A non-negative number indicating the high water mark of the stream using this queuing strategy.

-
-
+The Web IDL definition for the {{ByteLengthQueuingStrategy}} class is given as follows: -Any object with these properties can be used when a queuing strategy object is expected. However, we provide two -built-in queuing strategy classes that provide a common vocabulary for certain cases: {{ByteLengthQueuingStrategy}} and -{{CountQueuingStrategy}}. + +[Exposed=(Window,Worker,Worklet)] +interface ByteLengthQueuingStrategy { + constructor(QueuingStrategyInit init); -</div> + readonly attribute unrestricted double highWaterMark; + readonly attribute Function size; +}; + -

Class ByteLengthQueuingStrategy

+

Internal slots

-A common queuing strategy when dealing with bytes is to wait until the accumulated byteLength -properties of the incoming chunks reaches a specified high-water mark. As such, this is provided as a built-in -queuing strategy that can be used when constructing streams. +Instances of {{ByteLengthQueuingStrategy}} have a \[[highWaterMark]] internal slot, storing the +value given in the constructor. -
- When creating a readable stream or writable stream, you can supply a byte-length queuing strategy - directly: +
+ Additionally, every [=/global object=] |globalObject| has an associated byte length queuing + strategy size function, which is a {{Function}} whose value must be initialized as follows: -

-    const stream = new ReadableStream(
-      { ... },
-      new ByteLengthQueuingStrategy({ highWaterMark: 16 * 1024 })
-    );
-  
+ 1. Let |steps| be the following steps, given |chunk|: + 1. Return ? [$GetV$](|chunk|, "`byteLength`"). + 1. Let |F| be ! [$CreateBuiltinFunction$](|steps|, « », |globalObject|'s [=relevant Realm=]). + 1. Perform ! [$SetFunctionName$](|F|, "`size`"). + 1. Perform ! [$SetFunctionLength$](|F|, 1). + 1. Set |globalObject|'s [=byte length queuing strategy size function=] to a {{Function}} that + represents a reference to |F|, with [=callback context=] equal to |globalObject|'s [=relevant + settings object=]. - In this case, 16 KiB worth of chunks can be enqueued by the readable stream's underlying source before - the readable stream implementation starts sending backpressure signals to the underlying source. +

This design is somewhat historical. It is motivated by the desire to ensure that + {{ByteLengthQueuingStrategy/size}} is a function, not a method, i.e. it does not check its + this value. See whatwg/streams#1005 and heycam/webidl#819 for more background. +

-

-    const stream = new WritableStream(
-      { ... },
-      new ByteLengthQueuingStrategy({ highWaterMark: 32 * 1024 })
-    );
-  
+

Constructor and properties

- In this case, 32 KiB worth of chunks can be accumulated in the writable stream's internal queue, waiting for - previous writes to the underlying sink to finish, before the writable stream starts sending - backpressure signals to any producers. -
+
+
strategy = new {{ByteLengthQueuingStrategy/constructor(init)|ByteLengthQueuingStrategy}}({ {{QueuingStrategyInit/highWaterMark}} }) +
+

Creates a new {{ByteLengthQueuingStrategy}} with the provided [=high water mark=]. -

- It is not necessary to use {{ByteLengthQueuingStrategy}} with readable byte streams, as they always measure - chunks in bytes. Attempting to construct a byte stream with a {{ByteLengthQueuingStrategy}} will fail. -

+

Note that the provided high water mark will not be validated ahead of time. Instead, if it is + negative, NaN, or not a number, the resulting {{ByteLengthQueuingStrategy}} will cause the + corresponding stream constructor to throw. -

Class definition
+
highWaterMark = strategy.{{ByteLengthQueuingStrategy/highWaterMark}} +
+

Returns the [=high water mark=] provided to the constructor. -

+
strategy.{{ByteLengthQueuingStrategy/size}}(chunk) +
+

Measures the size of chunk by returning the value of its + byteLength property. +

-This section is non-normative. +
+ The ByteLengthQueuingStrategy(|init|) constructor steps are: -If one were to write the {{ByteLengthQueuingStrategy}} class in something close to the syntax of [[!ECMASCRIPT]], it -would look like + 1. Set [=this=].\[[highWaterMark]] to |init|["{{QueuingStrategyInit/highWaterMark}}"]. +
-

-  class ByteLengthQueuingStrategy {
-    constructor({ highWaterMark })
-    size(chunk)
-  }
-
+
+ The highWaterMark + attribute's getter steps are: + + 1. Return [=this=].\[[highWaterMark]]. +
-Each {{ByteLengthQueuingStrategy}} instance will additionally have an own data property -highWaterMark set by its constructor. +
+ The size attribute's getter + steps are: + 1. Return [=this=]'s [=relevant global object=]'s [=byte length queuing strategy size function=].
-
new -ByteLengthQueuingStrategy({ highWaterMark })
+

The {{CountQueuingStrategy}} class

-
- The constructor takes a non-negative number for the high-water mark, and stores it on the object as a property. +A common [=queuing strategy=] when dealing with streams of generic objects is to simply count the +number of chunks that have been accumulated so far, waiting until this number reaches a specified +high-water mark. As such, this strategy is also provided out of the box. + +
+ When creating a [=readable stream=] or [=writable stream=], you can supply a count queuing + strategy directly: + + + const stream = new ReadableStream( + { ... }, + new CountQueuingStrategy({ highWaterMark: 10 }) + ); + + + In this case, 10 [=chunks=] (of any kind) can be enqueued by the readable stream's [=underlying + source=] before the readable stream implementation starts sending [=backpressure=] signals to the + underlying source. + + + const stream = new WritableStream( + { ... }, + new CountQueuingStrategy({ highWaterMark: 5 }) + ); + + + In this case, five [=chunks=] (of any kind) can be accumulated in the writable stream's internal + queue, waiting for previous writes to the [=underlying sink=] to finish, before the writable + stream starts sending [=backpressure=] signals to any [=producers=].
- - 1. Perform ! CreateDataProperty(*this*, `"highWaterMark"`, _highWaterMark_). - +

Interface definition

-
Properties of the {{ByteLengthQueuingStrategy}} prototype
+The Web IDL definition for the {{CountQueuingStrategy}} class is given as follows: -
size(chunk)
+ +[Exposed=(Window,Worker,Worklet)] +interface CountQueuingStrategy { + constructor(QueuingStrategyInit init); -<div class="note"> - The <code>size</code> method returns the given chunk's <code>byteLength</code> property. (If the chunk doesn't have - one, it will return <emu-val>undefined</emu-val>, causing the stream using this strategy to error.) + readonly attribute unrestricted double highWaterMark; + readonly attribute Function size; +}; + + +

Internal slots

+ +Instances of {{CountQueuingStrategy}} have a \[[highWaterMark]] internal slot, storing the +value given in the constructor. + +
+ Additionally, every [=/global object=] |globalObject| has an associated count queuing strategy + size function, which is a {{Function}} whose value must be initialized as follows: + + 1. Let |steps| be the following steps: + 1. Return 1. + 1. Let |F| be ! [$CreateBuiltinFunction$](|steps|, « », |globalObject|'s [=relevant Realm=]). + 1. Perform ! [$SetFunctionName$](|F|, "`size`"). + 1. Perform ! [$SetFunctionLength$](|F|, 0). + 1. Set |globalObject|'s [=count queuing strategy size function=] to a {{Function}} that represents + a reference to |F|, with [=callback context=] equal to |globalObject|'s [=relevant settings + object=]. - This method is intentionally generic; it does not require that its this value be a - ByteLengthQueuingStrategy object. +

This design is somewhat historical. It is motivated by the desire to ensure that + {{CountQueuingStrategy/size}} is a function, not a method, i.e. it does not check its + this value. See whatwg/streams#1005 and heycam/webidl#819 for more background.

- - 1. Return ? GetV(_chunk_, `"byteLength"`). - +

Constructor and properties

-

Class CountQueuingStrategy

+
+
strategy = new {{CountQueuingStrategy/constructor(init)|CountQueuingStrategy}}({ {{QueuingStrategyInit/highWaterMark}} }) +
+

Creates a new {{CountQueuingStrategy}} with the provided [=high water mark=]. -A common queuing strategy when dealing with streams of generic objects is to simply count the number of chunks -that have been accumulated so far, waiting until this number reaches a specified high-water mark. As such, this -strategy is also provided out of the box. +

Note that the provided high water mark will not be validated ahead of time. Instead, if it is + negative, NaN, or not a number, the resulting {{CountQueuingStrategy}} will cause the + corresponding stream constructor to throw. -

- When creating a readable stream or writable stream, you can supply a count queuing strategy directly: +
highWaterMark = strategy.{{CountQueuingStrategy/highWaterMark}} +
+

Returns the [=high water mark=] provided to the constructor. + +

strategy.{{CountQueuingStrategy/size}}(chunk) +
+

Measures the size of chunk by always returning 1. This ensures that the total + queue size is a count of the number of chunks in the queue. +

-

-    const stream = new ReadableStream(
-      { ... },
-      new CountQueuingStrategy({ highWaterMark: 10 })
-    );
-  
+
+ The CountQueuingStrategy(|init|) constructor steps are: - In this case, 10 chunks (of any kind) can be enqueued by the readable stream's underlying source before - the readable stream implementation starts sending backpressure signals to the underlying source. + 1. Set [=this=].\[[highWaterMark]] to |init|["{{QueuingStrategyInit/highWaterMark}}"]. +
-

-    const stream = new WritableStream(
-      { ... },
-      new CountQueuingStrategy({ highWaterMark: 5 })
-    );
-  
+
+ The highWaterMark + attribute's getter steps are: - In this case, five chunks (of any kind) can be accumulated in the writable stream's internal queue, waiting - for previous writes to the underlying sink to finish, before the writable stream starts sending - backpressure signals to any producers. + 1. Return [=this=].\[[highWaterMark]].
-
Class definition
+
+ The size attribute's getter + steps are: -
+ 1. Return [=this=]'s [=relevant global object=]'s [=count queuing strategy size function=]. +
-This section is non-normative. +

Abstract operations

-If one were to write the {{CountQueuingStrategy}} class in something close to the syntax of [[!ECMASCRIPT]], it would -look like +The following algorithms are used by the stream constructors to extract the relevant pieces from +a {{QueuingStrategy}} dictionary. -

-  class CountQueuingStrategy {
-    constructor({ highWaterMark })
-    size(chunk)
-  }
-
+
+ ExtractHighWaterMark(|strategy|, |defaultHWM|) + performs the following steps: -Each {{CountQueuingStrategy}} instance will additionally have an own data property highWaterMark -set by its constructor. + 1. If |strategy|["{{QueuingStrategy/highWaterMark}}"] does not [=map/exist=], return |defaultHWM|. + 1. Let |highWaterMark| be |strategy|["{{QueuingStrategy/highWaterMark}}"]. + 1. If |highWaterMark| is NaN or |highWaterMark| < 0, throw a {{RangeError}} exception. + 1. Return |highWaterMark|. +

+∞ is explicitly allowed as a valid [=high water mark=]. It causes [=backpressure=] + to never be applied.

-
new -CountQueuingStrategy({ highWaterMark })
+
+ ExtractSizeAlgorithm(|strategy|) + performs the following steps: -
- The constructor takes a non-negative number for the high-water mark, and stores it on the object as a property. + 1. If |strategy|["{{QueuingStrategy/size}}"] does not [=map/exist=], return an algorithm that + returns 1. + 1. Return an algorithm that performs the following steps, taking a |chunk| argument: + 1. Return the result of [=invoke|invoking=] |strategy|["{{QueuingStrategy/size}}"] with argument + list « |chunk| ».
- - 1. Perform ! CreateDataProperty(*this*, `"highWaterMark"`, _highWaterMark_). - +

Supporting abstract operations

+ +The following abstract operations each support the implementation of more than one type of stream, +and as such are not grouped under the major sections above. + +

Queue-with-sizes

+ +The streams in this specification use a "queue-with-sizes" data structure to store queued up +values, along with their determined sizes. Various specification objects contain a +queue-with-sizes, represented by the object having two paired internal slots, always named +\[[queue]] and \[[queueTotalSize]]. \[[queue]] is a [=list=] of Records with \[[value]] and +\[[size]] fields, and \[[queueTotalSize]] is a JavaScript {{Number}}, i.e. a double-precision +floating point number. + +The following abstract operations are used when operating on objects that contain +queues-with-sizes, in order to ensure that the two internal slots stay synchronized. + +

Due to the limited precision of floating-point arithmetic, the framework +specified here, of keeping a running total in the \[[queueTotalSize]] slot, is not +equivalent to adding up the size of all [=chunks=] in \[[queue]]. (However, this only makes a +difference when there is a huge (~1015) variance in size between chunks, or when +trillions of chunks are enqueued.) + +

+ DequeueValue(|container|) performs the + following steps: + + 1. Assert: |container| has \[[queue]] and \[[queueTotalSize]] internal slots. + 1. Assert: |container|.\[[queue]] is not [=list/is empty|empty=]. + 1. Let |pair| be |container|.\[[queue]][0]. + 1. [=list/Remove=] |pair| from |container|.\[[queue]]. + 1. Set |container|.\[[queueTotalSize]] to |container|.\[[queueTotalSize]] − |pair|.\[[size]]. + 1. If |container|.\[[queueTotalSize]] < 0, set |container|.\[[queueTotalSize]] to 0. (This can + occur due to rounding errors.) + 1. Return |pair|.\[[value]]. +
-
Properties of the {{CountQueuingStrategy}} prototype
+
+ EnqueueValueWithSize(|container|, |value|, |size|) performs the + following steps: -
size()
+ 1. Assert: |container| has \[[queue]] and \[[queueTotalSize]] internal slots. + 1. If ! [$IsNonNegativeNumber$](|size|) is false, throw a {{RangeError}} exception. + 1. If |size| is +∞, throw a {{RangeError}} exception. + 1. [=list/Append=] Record {\[[value]]: |value|, \[[size]]: |size|} to |container|.\[[queue]]. + 1. Set |container|.\[[queueTotalSize]] to |container|.\[[queueTotalSize]] + |size|. +
-
- The size method returns one always, so that the total queue size is a count of the number of chunks in - the queue. - - This method is intentionally generic; it does not require that its this value be a - CountQueuingStrategy object. -
- - - 1. Return *1*. - - -

Queue-with-sizes operations

- -The streams in this specification use a "queue-with-sizes" data structure to store queued up values, along with their -determined sizes. Various specification objects contain a queue-with-sizes, represented by the object having two paired -internal slots, always named \[[queue]] and \[[queueTotalSize]]. \[[queue]] is a List of Records with \[[value]] and -\[[size]] fields, and \[[queueTotalSize]] is a JavaScript {{Number}}, i.e. a double-precision floating point number. - -The following abstract operations are used when operating on objects that contain queues-with-sizes, in order to ensure -that the two internal slots stay synchronized. - -

Due to the limited precision of floating-point arithmetic, the framework specified here, of keeping a -running total in the \[[queueTotalSize]] slot, is not equivalent to adding up the size of all chunks in -\[[queue]]. (However, this only makes a difference when there is a huge (~1015) variance in size between -chunks, or when trillions of chunks are enqueued.)

- -

DequeueValue ( container )

- - - 1. Assert: _container_ has [[queue]] and [[queueTotalSize]] internal slots. - 1. Assert: _container_.[[queue]] is not empty. - 1. Let _pair_ be the first element of _container_.[[queue]]. - 1. Remove _pair_ from _container_.[[queue]], shifting all other elements downward (so that the second becomes the - first, and so on). - 1. Set _container_.[[queueTotalSize]] to _container_.[[queueTotalSize]] − _pair_.[[size]]. - 1. If _container_.[[queueTotalSize]] < *0*, set _container_.[[queueTotalSize]] to *0*. (This can occur due to - rounding errors.) - 1. Return _pair_.[[value]]. - - -

EnqueueValueWithSize ( container, -value, size )

- - - 1. Assert: _container_ has [[queue]] and [[queueTotalSize]] internal slots. - 1. Let _size_ be ? ToNumber(_size_). - 1. If ! IsFiniteNonNegativeNumber(_size_) is *false*, throw a *RangeError* exception. - 1. Append Record {[[value]]: _value_, [[size]]: _size_} as the last element of _container_.[[queue]]. - 1. Set _container_.[[queueTotalSize]] to _container_.[[queueTotalSize]] + _size_. - - -

PeekQueueValue ( container )

- - - 1. Assert: _container_ has [[queue]] and [[queueTotalSize]] internal slots. - 1. Assert: _container_.[[queue]] is not empty. - 1. Let _pair_ be the first element of _container_.[[queue]]. - 1. Return _pair_.[[value]]. - - -

ResetQueue ( container )

- - - 1. Assert: _container_ has [[queue]] and [[queueTotalSize]] internal slots. - 1. Set _container_.[[queue]] to a new empty List. - 1. Set _container_.[[queueTotalSize]] to *0*. - - -

Miscellaneous operations

- -A few abstract operations are used in this specification for utility purposes. We define them here. - -

CreateAlgorithmFromUnderlyingMethod ( underlyingObject, methodName, -algoArgCount, extraArgs )

- - - 1. Assert: _underlyingObject_ is not *undefined*. - 1. Assert: ! IsPropertyKey(_methodName_) is *true*. - 1. Assert: _algoArgCount_ is *0* or *1*. - 1. Assert: _extraArgs_ is a List. - 1. Let _method_ be ? GetV(_underlyingObject_, _methodName_). - 1. If _method_ is not *undefined*, - 1. If ! IsCallable(_method_) is *false*, throw a *TypeError* exception. - 1. If _algoArgCount_ is *0*, return an algorithm that performs the following steps: - 1. Return ! PromiseCall(_method_, _underlyingObject_, _extraArgs_). - 1. Otherwise, return an algorithm that performs the following steps, taking an _arg_ argument: - 1. Let _fullArgs_ be a List consisting of _arg_ followed by the elements of _extraArgs_ in order. - 1. Return ! PromiseCall(_method_, _underlyingObject_, _fullArgs_). - 1. Return an algorithm which returns a promise resolved with *undefined*. - - -

InvokeOrNoop ( O, P, args -)

+
+ PeekQueueValue(|container|) + performs the following steps: -
- InvokeOrNoop is a slight modification of the [[!ECMASCRIPT]] Invoke abstract operation to return - undefined when the method is not present. + 1. Assert: |container| has \[[queue]] and \[[queueTotalSize]] internal slots. + 1. Assert: |container|.\[[queue]] is not [=list/is empty|empty=]. + 1. Let |pair| be |container|.\[[queue]][0]. + 1. Return |pair|.\[[value]].
- - 1. Assert: _O_ is not *undefined*. - 1. Assert: ! IsPropertyKey(_P_) is *true*. - 1. Assert: _args_ is a List. - 1. Let _method_ be ? GetV(_O_, _P_). - 1. If _method_ is *undefined*, return *undefined*. - 1. Return ? Call(_method_, _O_, _args_). - +
+ ResetQueue(|container|) + performs the following steps: -

IsFiniteNonNegativeNumber ( v -)

- - - 1. If ! IsNonNegativeNumber(_v_) is *false*, return *false*. - 1. If _v_ is *+∞*, return *false*. - 1. Return *true*. - + 1. Assert: |container| has \[[queue]] and \[[queueTotalSize]] internal slots. + 1. Set |container|.\[[queue]] to a new empty [=list=]. + 1. Set |container|.\[[queueTotalSize]] to 0. +
-

IsNonNegativeNumber ( v )

+

Miscellaneous

- - 1. If Type(_v_) is not Number, return *false*. - 1. If _v_ is *NaN*, return *false*. - 1. If _v_ < *0*, return *false*. - 1. Return *true*. - +The following abstract operations are a grab-bag of utilities. -

PromiseCall ( F, V, -args )

+
+ IsNonNegativeNumber(|v|) performs the following steps: -
- This encapsulates the relevant promise-related parts of the Web IDL call a user object's operation algorithm - for use while we work on moving to Web IDL. -
- - - 1. Assert: ! IsCallable(_F_) is *true*. - 1. Assert: _V_ is not *undefined*. - 1. Assert: _args_ is a List. - 1. Let _returnValue_ be Call(_F_, _V_, _args_). - 1. If _returnValue_ is an abrupt completion, return a promise rejected with _returnValue_.[[Value]]. - 1. Otherwise, return a promise resolved with _returnValue_.[[Value]]. - - -

TransferArrayBuffer ( O )

- - - 1. Assert: Type(_O_) is Object. - 1. Assert: _O_ has an [[ArrayBufferData]] internal slot. - 1. Assert: ! IsDetachedBuffer(_O_) is *false*. - 1. Let _arrayBufferData_ be _O_.[[ArrayBufferData]]. - 1. Let _arrayBufferByteLength_ be _O_.[[ArrayBufferByteLength]]. - 1. Perform ! DetachArrayBuffer(_O_). - 1. Return a new ArrayBuffer object (created in the current Realm Record) whose - [[ArrayBufferData]] internal slot value is _arrayBufferData_ and whose [[ArrayBufferByteLength]] internal slot - value is _arrayBufferByteLength_. - - -

ValidateAndNormalizeHighWaterMark ( highWaterMark )

- - - 1. Set _highWaterMark_ to ? ToNumber(_highWaterMark_). - 1. If _highWaterMark_ is *NaN* or _highWaterMark_ < *0*, throw a *RangeError* exception. -

*+∞* is explicitly allowed as a valid high water mark. It causes backpressure to never be applied.

- 1. Return _highWaterMark_. -
- -

MakeSizeAlgorithmFromSizeFunction ( size )

- - - 1. If _size_ is *undefined*, return an algorithm that returns *1*. - 1. If ! IsCallable(_size_) is *false*, throw a *TypeError* exception. - 1. Return an algorithm that performs the following steps, taking a _chunk_ argument: - 1. Return ? Call(_size_, *undefined*, « _chunk_ »). - - -

Global properties

- -The following constructors must be exposed on the global object as data properties of the same name: - -
    -
  • {{ReadableStream}} -
  • {{WritableStream}} -
  • {{TransformStream}} -
  • {{ByteLengthQueuingStrategy}} -
  • {{CountQueuingStrategy}} -
- -The attributes of these properties must be { \[[Writable]]: true, \[[Enumerable]]: false, -\[[Configurable]]: true }. + 1. If [$Type$](|v|) is not Number, return false. + 1. If |v| is NaN, return false. + 1. If |v| < 0, return false. + 1. Return true. +
-
- The {{ReadableStreamDefaultReader}}, {{ReadableStreamBYOBReader}}, {{ReadableStreamDefaultController}}, - {{ReadableByteStreamController}}, {{WritableStreamDefaultWriter}}, {{WritableStreamDefaultController}}, and - {{TransformStreamDefaultController}} classes are specifically not exposed, as they are not independently useful. +
+ TransferArrayBuffer(|O|) performs the following steps: + + 1. Assert: [$Type$](|O|) is Object. + 1. Assert: |O| has an \[[ArrayBufferData]] internal slot. + 1. Assert: ! [$IsDetachedBuffer$](|O|) is false. + 1. Let |arrayBufferData| be |O|.\[[ArrayBufferData]]. + 1. Let |arrayBufferByteLength| be |O|.\[[ArrayBufferByteLength]]. + 1. Perform ! [$DetachArrayBuffer$](|O|). + 1. Return a new {{ArrayBuffer}} object, created in [=the current Realm=], whose + \[[ArrayBufferData]] internal slot value is |arrayBufferData| and whose + \[[ArrayBufferByteLength]] internal slot value is |arrayBufferByteLength|.

Examples of creating streams

@@ -5458,663 +5679,654 @@ The attributes of these properties must be { \[[Writable]]: trueThis section, and all its subsections, are non-normative. -The previous examples throughout the standard have focused on how to use streams. Here we show how to create a stream, -using the {{ReadableStream}} or {{WritableStream}} constructors. +The previous examples throughout the standard have focused on how to use streams. Here we show how +to create a stream, using the {{ReadableStream}}, {{WritableStream}}, and {{TransformStream}} +constructors. -

A readable stream with an underlying push source (no backpressure support)

+

A readable stream with an underlying push source (no +backpressure support)

-The following function creates readable streams that wrap {{WebSocket}} instances [[HTML]], which are push sources -that do not support backpressure signals. It illustrates how, when adapting a push source, usually most of the work -happens in the {{underlying source/start()}} function. +The following function creates [=readable streams=] that wrap {{WebSocket}} instances [[HTML]], +which are [=push sources=] that do not support backpressure signals. It illustrates how, when +adapting a push source, usually most of the work happens in the {{UnderlyingSource/start|start()}} +method. -

-  function makeReadableWebSocketStream(url, protocols) {
-    const ws = new WebSocket(url, protocols);
-    ws.binaryType = "arraybuffer";
+
+function makeReadableWebSocketStream(url, protocols) {
+  const ws = new WebSocket(url, protocols);
+  ws.binaryType = "arraybuffer";
 
-    return new ReadableStream({
-      start(controller) {
-        ws.onmessage = event => controller.enqueue(event.data);
-        ws.onclose = () => controller.close();
-        ws.onerror = () => controller.error(new Error("The WebSocket errored!"));
-      },
+  return new ReadableStream({
+    start(controller) {
+      ws.onmessage = event => controller.enqueue(event.data);
+      ws.onclose = () => controller.close();
+      ws.onerror = () => controller.error(new Error("The WebSocket errored!"));
+    },
 
-      cancel() {
-        ws.close();
-      }
-    });
-  }
-</code></pre>
+    cancel() {
+      ws.close();
+    }
+  });
+}
+
 
-We can then use this function to create readable streams for a web socket, and pipe that stream to an arbitrary
-writable stream:
+We can then use this function to create readable streams for a web socket, and pipe that stream to
+an arbitrary writable stream:
 
-

-  const webSocketStream = makeReadableWebSocketStream("wss://example.com:443/", "protocol");
+
+const webSocketStream = makeReadableWebSocketStream("wss://example.com:443/", "protocol");
 
-  webSocketStream.pipeTo(writableStream)
-    .then(() => console.log("All data successfully written!"))
-    .catch(e => console.error("Something went wrong!", e));
-</code></pre>
+webSocketStream.pipeTo(writableStream)
+  .then(() => console.log("All data successfully written!"))
+  .catch(e => console.error("Something went wrong!", e));
+
 
 
- This specific style of wrapping a web socket interprets web socket messages directly as chunks. - This can be a convenient abstraction, for example when piping to a writable stream or transform - stream for which each web socket message makes sense as a chunk to consume or transform. - - However, often when people talk about "adding streams support to web sockets", they are hoping instead for a new - capability to send an individual web socket message in a streaming fashion, so that e.g. a file could be transferred - in a single message without holding all of its contents in memory on the client side. To accomplish this goal, we'd - instead want to allow individual web socket messages to themselves be {{ReadableStream}} instances. That isn't what we - show in the above example. - - For more background, see this - discussion. + This specific style of wrapping a web socket interprets web socket messages directly as + [=chunks=]. This can be a convenient abstraction, for example when [=piping=] to a [=writable + stream=] or [=transform stream=] for which each web socket message makes sense as a chunk to + consume or transform. + + However, often when people talk about "adding streams support to web sockets", they are hoping + instead for a new capability to send an individual web socket message in a streaming fashion, so + that e.g. a file could be transferred in a single message without holding all of its contents in + memory on the client side. To accomplish this goal, we'd instead want to allow individual web + socket messages to themselves be {{ReadableStream}} instances. That isn't what we show in the + above example. + + For more background, see this discussion.
-

A readable stream with an underlying push source and backpressure support

+

A readable stream with an underlying push source and +backpressure support

-The following function returns readable streams that wrap "backpressure sockets," which are hypothetical objects -that have the same API as web sockets, but also provide the ability to pause and resume the flow of data with their -readStop and readStart methods. In doing so, this example shows how to apply -backpressure to underlying sources that support it. +The following function returns [=readable streams=] that wrap "backpressure sockets," which are +hypothetical objects that have the same API as web sockets, but also provide the ability to pause +and resume the flow of data with their readStop and readStart methods. In +doing so, this example shows how to apply [=backpressure=] to [=underlying sources=] that support +it. -

-  function makeReadableBackpressureSocketStream(host, port) {
-    const socket = createBackpressureSocket(host, port);
+
+function makeReadableBackpressureSocketStream(host, port) {
+  const socket = createBackpressureSocket(host, port);
 
-    return new ReadableStream({
-      start(controller) {
-        socket.ondata = event => {
-          controller.enqueue(event.data);
+  return new ReadableStream({
+    start(controller) {
+      socket.ondata = event => {
+        controller.enqueue(event.data);
 
-          if (controller.desiredSize <= 0) {
-            // The internal queue is full, so propagate
-            // the backpressure signal to the underlying source.
-            socket.readStop();
-          }
-        };
+        if (controller.desiredSize <= 0) {
+          // The internal queue is full, so propagate
+          // the backpressure signal to the underlying source.
+          socket.readStop();
+        }
+      };
 
-        socket.onend = () => controller.close();
-        socket.onerror = () => controller.error(new Error("The socket errored!"));
-      },
+      socket.onend = () => controller.close();
+      socket.onerror = () => controller.error(new Error("The socket errored!"));
+    },
 
-      pull() {
-        // This is called if the internal queue has been emptied, but the
-        // stream's consumer still wants more data. In that case, restart
-        // the flow of data if we have previously paused it.
-        socket.readStart();
-      },
+    pull() {
+      // This is called if the internal queue has been emptied, but the
+      // stream's consumer still wants more data. In that case, restart
+      // the flow of data if we have previously paused it.
+      socket.readStart();
+    },
 
-      cancel() {
-        socket.close();
-      }
-    });
-  }
-</code></pre>
-
-We can then use this function to create readable streams for such "backpressure sockets" in the same way we do for web
-sockets. This time, however, when we pipe to a destination that cannot accept data as fast as the socket is producing
-it, or if we leave the stream alone without reading from it for some time, a backpressure signal will be sent to the
-socket.
-
-<h3 id="example-rbs-push">A readable byte stream with an underlying push source (no backpressure support)</h3>
-
-The following function returns <a>readable byte streams</a> that wraps a hypothetical UDP socket API, including a
-promise-returning <code>select2()</code> method that is meant to be evocative of the POSIX select(2) system call.
-
-Since the UDP protocol does not have any built-in backpressure support, the backpressure signal given by
-{{ReadableByteStreamController/desiredSize}} is ignored, and the stream ensures that when data is available from the
-socket but not yet requested by the developer, it is enqueued in the stream's <a>internal queue</a>, to avoid overflow
-of the kernel-space queue and a consequent loss of data.
-
-This has some interesting consequences for how <a>consumers</a> interact with the stream. If the consumer does not read
-data as fast as the socket produces it, the <a>chunks</a> will remain in the stream's <a>internal queue</a>
-indefinitely. In this case, using a <a>BYOB reader</a> will cause an extra copy, to move the data from the stream's
-internal queue to the developer-supplied buffer. However, if the consumer consumes the data quickly enough, a <a>BYOB
-reader</a> will allow zero-copy reading directly into developer-supplied buffers.
-
-(You can imagine a more complex version of this example which uses {{ReadableByteStreamController/desiredSize}} to
-inform an out-of-band backpressure signaling mechanism, for example by sending a message down the socket to adjust the
-rate of data being sent. That is left as an exercise for the reader.)
-
-<pre><code class="lang-javascript">
-  const DEFAULT_CHUNK_SIZE = 65536;
-
-  function makeUDPSocketStream(host, port) {
-    const socket = createUDPSocket(host, port);
-
-    return new ReadableStream({
-      type: "bytes",
-
-      start(controller) {
-        readRepeatedly().catch(e => controller.error(e));
-
-        function readRepeatedly() {
-          return socket.select2().then(() => {
-            // Since the socket can become readable even when there’s
-            // no pending BYOB requests, we need to handle both cases.
-            let bytesRead;
-            if (controller.byobRequest) {
-              const v = controller.byobRequest.view;
-              bytesRead = socket.readInto(v.buffer, v.byteOffset, v.byteLength);
-              controller.byobRequest.respond(bytesRead);
-            } else {
-              const buffer = new ArrayBuffer(DEFAULT_CHUNK_SIZE);
-              bytesRead = socket.readInto(buffer, 0, DEFAULT_CHUNK_SIZE);
-              controller.enqueue(new Uint8Array(buffer, 0, bytesRead));
-            }
-
-            if (bytesRead === 0) {
-              controller.close();
-              return;
-            }
-
-            return readRepeatedly();
-          });
-        }
-      },
+    cancel() {
+      socket.close();
+    }
+  });
+}
+
 
-      cancel() {
-        socket.close();
-      }
-    });
-  }
-
+We can then use this function to create readable streams for such "backpressure sockets" in the +same way we do for web sockets. This time, however, when we pipe to a destination that cannot +accept data as fast as the socket is producing it, or if we leave the stream alone without reading +from it for some time, a backpressure signal will be sent to the socket. -{{ReadableStream}} instances returned from this function can now vend BYOB readers, with all of the -aforementioned benefits and caveats. +

A readable byte stream with an underlying push source (no backpressure +support)

-

A readable stream with an underlying pull source

+The following function returns [=readable byte streams=] that wraps a hypothetical UDP socket API, +including a promise-returning select2() method that is meant to be evocative of the +POSIX select(2) system call. -The following function returns readable streams that wrap portions of the -Node.js file system API (which themselves map fairly directly to C's -fopen, fread, and fclose trio). Files are a typical example of pull -sources. Note how in contrast to the examples with push sources, most of the work here happens on-demand in the -{{underlying source/pull()}} function, and not at startup time in the {{underlying source/start()}} function. +Since the UDP protocol does not have any built-in backpressure support, the backpressure signal +given by {{ReadableByteStreamController/desiredSize}} is ignored, and the stream ensures that when +data is available from the socket but not yet requested by the developer, it is enqueued in the +stream's [=internal queue=], to avoid overflow of the kernel-space queue and a consequent loss of +data. -

-  const fs = require("pr/fs"); // https://github.com/jden/pr
-  const CHUNK_SIZE = 1024;
+This has some interesting consequences for how [=consumers=] interact with the stream. If the
+consumer does not read data as fast as the socket produces it, the [=chunks=] will remain in the
+stream's [=internal queue=] indefinitely. In this case, using a [=BYOB reader=] will cause an extra
+copy, to move the data from the stream's internal queue to the developer-supplied buffer. However,
+if the consumer consumes the data quickly enough, a [=BYOB reader=] will allow zero-copy reading
+directly into developer-supplied buffers.
 
-  function makeReadableFileStream(filename) {
-    let fd;
-    let position = 0;
+(You can imagine a more complex version of this example which uses
+{{ReadableByteStreamController/desiredSize}} to inform an out-of-band backpressure signaling
+mechanism, for example by sending a message down the socket to adjust the rate of data being sent.
+That is left as an exercise for the reader.)
 
-    return new ReadableStream({
-      start() {
-        return fs.open(filename, "r").then(result => {
-          fd = result;
-        });
-      },
+
+const DEFAULT_CHUNK_SIZE = 65536;
 
-      pull(controller) {
-        const buffer = new ArrayBuffer(CHUNK_SIZE);
+function makeUDPSocketStream(host, port) {
+  const socket = createUDPSocket(host, port);
 
-        return fs.read(fd, buffer, 0, CHUNK_SIZE, position).then(bytesRead => {
-          if (bytesRead === 0) {
-            return fs.close(fd).then(() => controller.close());
+  return new ReadableStream({
+    type: "bytes",
+
+    start(controller) {
+      readRepeatedly().catch(e => controller.error(e));
+
+      function readRepeatedly() {
+        return socket.select2().then(() => {
+          // Since the socket can become readable even when there’s
+          // no pending BYOB requests, we need to handle both cases.
+          let bytesRead;
+          if (controller.byobRequest) {
+            const v = controller.byobRequest.view;
+            bytesRead = socket.readInto(v.buffer, v.byteOffset, v.byteLength);
+            controller.byobRequest.respond(bytesRead);
           } else {
-            position += bytesRead;
+            const buffer = new ArrayBuffer(DEFAULT_CHUNK_SIZE);
+            bytesRead = socket.readInto(buffer, 0, DEFAULT_CHUNK_SIZE);
             controller.enqueue(new Uint8Array(buffer, 0, bytesRead));
           }
-        });
-      },
 
-      cancel() {
-        return fs.close(fd);
-      }
-    });
-  }
-</code></pre>
+          if (bytesRead === 0) {
+            controller.close();
+            return;
+          }
 
-We can then create and use readable streams for files just as we could before for sockets.
+          return readRepeatedly();
+        });
+      }
+    },
 
-<h3 id="example-rbs-pull">A readable byte stream with an underlying pull source</h3>
+    cancel() {
+      socket.close();
+    }
+  });
+}
+
 
-The following function returns readable byte streams that allow efficient zero-copy reading of files, again
-using the Node.js file system API. Instead of using a predetermined chunk
-size of 1024, it attempts to fill the developer-supplied buffer, allowing full control.
+{{ReadableStream}} instances returned from this function can now vend [=BYOB readers=], with all of
+the aforementioned benefits and caveats.
 
-

-  const fs = require("pr/fs"); // https://github.com/jden/pr
-  const DEFAULT_CHUNK_SIZE = 1024;
+

A readable stream with an underlying pull source

- function makeReadableByteFileStream(filename) { - let fd; - let position = 0; +The following function returns [=readable streams=] that wrap portions of the Node.js file system API (which themselves map fairly +directly to C's fopen, fread, and fclose trio). Files are a +typical example of [=pull sources=]. Note how in contrast to the examples with push sources, most +of the work here happens on-demand in the {{UnderlyingSource/pull|pull()}} function, and not at +startup time in the {{UnderlyingSource/start|start()}} function. + + +const fs = require("fs").promises; +const CHUNK_SIZE = 1024; + +function makeReadableFileStream(filename) { + let fileHandle; + let position = 0; + + return new ReadableStream({ + async start() { + fileHandle = await fs.open(filename, "r"); + }, + + async pull(controller) { + const buffer = new ArrayBuffer(CHUNK_SIZE); + + const { bytesRead } = await fileHandle.read(buffer, 0, CHUNK_SIZE, position); + if (bytesRead === 0) { + await fileHandle.close(); + controller.close(); + } else { + position += bytesRead; + controller.enqueue(new Uint8Array(buffer, 0, bytesRead)); + } + }, - return new ReadableStream({ - type: "bytes", + cancel() { + return fileHandle.close(); + } + }); +} + - start() { - return fs.open(filename, "r").then(result => { - fd = result; - }); - }, +We can then create and use readable streams for files just as we could before for sockets. - pull(controller) { - // Even when the consumer is using the default reader, the auto-allocation - // feature allocates a buffer and passes it to us via byobRequest. - const v = controller.byobRequest.view; +

A readable byte stream with an underlying pull source

- return fs.read(fd, v.buffer, v.byteOffset, v.byteLength, position).then(bytesRead => { - if (bytesRead === 0) { - return fs.close(fd).then(() => controller.close()); - } else { - position += bytesRead; - controller.byobRequest.respond(bytesRead); - } - }); - }, +The following function returns [=readable byte streams=] that allow efficient zero-copy reading of +files, again using the Node.js file system API. +Instead of using a predetermined chunk size of 1024, it attempts to fill the developer-supplied +buffer, allowing full control. + + +const fs = require("fs").promises; +const DEFAULT_CHUNK_SIZE = 1024; + +function makeReadableByteFileStream(filename) { + let fileHandle; + let position = 0; + + return new ReadableStream({ + type: "bytes", + + async start() { + fileHandle = await fs.open(filename, "r"); + }, + + pull(controller) { + // Even when the consumer is using the default reader, the auto-allocation + // feature allocates a buffer and passes it to us via byobRequest. + const v = controller.byobRequest.view; + + const { bytesRead } = await fileHandle.read(v.buffer, v.byteOffset, v.byteLength); + if (bytesRead === 0) { + await fileHandle.close(); + controller.close(); + } else { + position += bytesRead; + controller.byobRequest.respond(bytesRead); + } + }, - cancel() { - return fs.close(fd); - }, + cancel() { + return fs.close(fd); + }, - autoAllocateChunkSize: DEFAULT_CHUNK_SIZE - }); - } -</code></pre> + autoAllocateChunkSize: DEFAULT_CHUNK_SIZE + }); +} + -With this in hand, we can create and use BYOB readers for the returned {{ReadableStream}}. But we can -also create default readers, using them in the same simple and generic manner as usual. The adaptation between -the low-level byte tracking of the underlying byte source shown here, and the higher-level chunk-based -consumption of a default reader, is all taken care of automatically by the streams implementation. The -auto-allocation feature, via the autoAllocateChunkSize option, even allows -us to write less code, compared to the manual branching in [[#example-rbs-push]]. +With this in hand, we can create and use [=BYOB readers=] for the returned {{ReadableStream}}. But +we can also create [=default readers=], using them in the same simple and generic manner as usual. +The adaptation between the low-level byte tracking of the [=underlying byte source=] shown here, +and the higher-level chunk-based consumption of a [=default reader=], is all taken care of +automatically by the streams implementation. The auto-allocation feature, via the +{{UnderlyingSource/autoAllocateChunkSize}} option, even allows us to write less code, compared to +the manual branching in [[#example-rbs-push]].

A writable stream with no backpressure or success signals

-The following function returns a writable stream that wraps a {{WebSocket}} [[HTML]]. Web sockets do not provide -any way to tell when a given chunk of data has been successfully sent (without awkward polling of -{{WebSocket/bufferedAmount}}, which we leave as an exercise to the reader). As such, this writable stream has no ability -to communicate accurate backpressure signals or write success/failure to its producers. That is, the -promises returned by its writer's {{WritableStreamDefaultWriter/write()}} method and +The following function returns a [=writable stream=] that wraps a {{WebSocket}} [[HTML]]. Web +sockets do not provide any way to tell when a given chunk of data has been successfully sent +(without awkward polling of {{WebSocket/bufferedAmount}}, which we leave as an exercise to the +reader). As such, this writable stream has no ability to communicate accurate [=backpressure=] +signals or write success/failure to its [=producers=]. That is, the promises returned by its +[=writer=]'s {{WritableStreamDefaultWriter/write()}} method and {{WritableStreamDefaultWriter/ready}} getter will always fulfill immediately. -

-  function makeWritableWebSocketStream(url, protocols) {
-    const ws = new WebSocket(url, protocols);
-
-    return new WritableStream({
-      start(controller) {
-        ws.onerror = () => {
-          controller.error(new Error("The WebSocket errored!"));
-          ws.onclose = null;
-        };
-        ws.onclose = () => controller.error(new Error("The server closed the connection unexpectedly!"));
-        return new Promise(resolve => ws.onopen = resolve);
-      },
-
-      write(chunk) {
-        ws.send(chunk);
-        // Return immediately, since the web socket gives us no easy way to tell
-        // when the write completes.
-      },
-
-      close() {
-        return closeWS(1000);
-      },
-
-      abort(reason) {
-        return closeWS(4000, reason && reason.message);
-      },
-    });
-
-    function closeWS(code, reasonString) {
-      return new Promise((resolve, reject) => {
-        ws.onclose = e => {
-          if (e.wasClean) {
-            resolve();
-          } else {
-            reject(new Error("The connection was not closed cleanly"));
-          }
-        };
-        ws.close(code, reasonString);
-      });
-    }
-  }
-
- -We can then use this function to create writable streams for a web socket, and pipe an arbitrary readable stream to it: + +function makeWritableWebSocketStream(url, protocols) { + const ws = new WebSocket(url, protocols); -<pre><code class="lang-javascript"> - const webSocketStream = makeWritableWebSocketStream("wss://example.com:443/", "protocol"); - - readableStream.pipeTo(webSocketStream) - .then(() => console.log("All data successfully written!")) - .catch(e => console.error("Something went wrong!", e)); -</code></pre> + return new WritableStream({ + start(controller) { + ws.onerror = () => { + controller.error(new Error("The WebSocket errored!")); + ws.onclose = null; + }; + ws.onclose = () => controller.error(new Error("The server closed the connection unexpectedly!")); + return new Promise(resolve => ws.onopen = resolve); + }, -<p class="note">See <a href="#note-web-socket-wrapping-examples">the earlier note</a> about this style of wrapping web -sockets into streams.</p> + write(chunk) { + ws.send(chunk); + // Return immediately, since the web socket gives us no easy way to tell + // when the write completes. + }, -<h3 id="example-ws-backpressure">A writable stream with backpressure and success signals</h3> + close() { + return closeWS(1000); + }, -The following function returns <a>writable streams</a> that wrap portions of the <a -href="https://nodejs.org/api/fs.html">Node.js file system API</a> (which themselves map fairly directly to C's -<code>fopen</code>, <code>fwrite</code>, and <code>fclose</code> trio). Since the API we are wrapping provides a way to -tell when a given write succeeds, this stream will be able to communicate <a>backpressure</a> signals as well as whether -an individual write succeeded or failed. + abort(reason) { + return closeWS(4000, reason && reason.message); + }, + }); -<pre><code class="lang-javascript"> - const fs = require("pr/fs"); // https://github.com/jden/pr + function closeWS(code, reasonString) { + return new Promise((resolve, reject) => { + ws.onclose = e => { + if (e.wasClean) { + resolve(); + } else { + reject(new Error("The connection was not closed cleanly")); + } + }; + ws.close(code, reasonString); + }); + } +} + - function makeWritableFileStream(filename) { - let fd; +We can then use this function to create writable streams for a web socket, and pipe an arbitrary +readable stream to it: - return new WritableStream({ - start() { - return fs.open(filename, "w").then(result => { - fd = result; - }); - }, + +const webSocketStream = makeWritableWebSocketStream("wss://example.com:443/", "protocol"); - write(chunk) { - return fs.write(fd, chunk, 0, chunk.length); - }, +readableStream.pipeTo(webSocketStream) + .then(() => console.log("All data successfully written!")) + .catch(e => console.error("Something went wrong!", e)); + - close() { - return fs.close(fd); - }, +

See the earlier note about this +style of wrapping web sockets into streams. - abort() { - return fs.close(fd); - } - }); - } -

+

A writable stream with backpressure and success signals

-We can then use this function to create a writable stream for a file, and write individual chunks of data to it: +The following function returns [=writable streams=] that wrap portions of the Node.js file system API (which themselves map fairly +directly to C's fopen, fwrite, and fclose trio). Since the +API we are wrapping provides a way to tell when a given write succeeds, this stream will be able to +communicate [=backpressure=] signals as well as whether an individual write succeeded or failed. -

-  const fileStream = makeWritableFileStream("/example/path/on/fs.txt");
-  const writer = fileStream.getWriter();
+
+const fs = require("fs").promises;
 
-  writer.write("To stream, or not to stream\n");
-  writer.write("That is the question\n");
+function makeWritableFileStream(filename) {
+  let fileHandle;
 
-  writer.close()
-    .then(() => console.log("chunks written and stream closed successfully!"))
-    .catch(e => console.error(e));
-</code></pre>
+  return new WritableStream({
+    async start() {
+      fileHandle = await fs.open(filename, "w");
+    },
 
-Note that if a particular call to <code>fs.write</code> takes a longer time, the returned promise will fulfill later.
-In the meantime, additional writes can be queued up, which are stored in the stream's internal queue. The accumulation
-of chunks in this queue can change the stream to return a pending promise from the {{WritableStreamDefaultWriter/ready}}
-getter, which is a signal to <a>producers</a> that they would benefit from backing off and stopping writing, if
-possible.
+    write(chunk) {
+      return fileHandle.write(chunk, 0, chunk.length);
+    },
 
-The way in which the writable stream queues up writes is especially important in this case, since as stated in
-<a href="https://nodejs.org/api/fs.html#fs_fs_write_fd_data_position_encoding_callback">the documentation for
-<code>fs.write</code></a>, "it is unsafe to use <code>fs.write</code> multiple times on the same file without waiting
-for the [promise]." But we don't have to worry about that when writing the <code>makeWritableFileStream</code>
-function, since the stream implementation guarantees that the <a>underlying sink</a>'s {{underlying sink/write()}}
-method will not be called until any promises returned by previous calls have fulfilled!
+    close() {
+      return fs.close(fd);
+    },
 
-<h3 id="example-both">A { readable, writable } stream pair wrapping the same underlying resource</h3>
+    abort() {
+      return fs.close(fd);
+    }
+  });
+}
+
+
+We can then use this function to create a writable stream for a file, and write individual
+[=chunks=] of data to it:
+
+
+const fileStream = makeWritableFileStream("/example/path/on/fs.txt");
+const writer = fileStream.getWriter();
+
+writer.write("To stream, or not to stream\n");
+writer.write("That is the question\n");
+
+writer.close()
+  .then(() => console.log("chunks written and stream closed successfully!"))
+  .catch(e => console.error(e));
+
+
+Note that if a particular call to fileHandle.write takes a longer time, the returned
+promise will fulfill later. In the meantime, additional writes can be queued up, which are stored
+in the stream's internal queue. The accumulation of chunks in this queue can change the stream to
+return a pending promise from the {{WritableStreamDefaultWriter/ready}} getter, which is a signal
+to [=producers=] that they would benefit from backing off and stopping writing, if possible.
+
+The way in which the writable stream queues up writes is especially important in this case, since
+as stated in the
+documentation for fileHandle.write, "it is unsafe to use
+filehandle.write multiple times on the same file without waiting for the promise." But
+we don't have to worry about that when writing the makeWritableFileStream function,
+since the stream implementation guarantees that the [=underlying sink=]'s
+{{UnderlyingSink/write|write()}} method will not be called until any promises returned by previous
+calls have fulfilled!
+
+

A { readable, writable } stream pair wrapping the same underlying +resource

The following function returns an object of the form { readable, writable }, with the -readable property containing a readable stream and the writable property containing a -writable stream, where both streams wrap the same underlying web socket resource. In essence, this combines -[[#example-rs-push-no-backpressure]] and [[#example-ws-no-backpressure]]. - -While doing so, it illustrates how you can use JavaScript classes to create reusable underlying sink and underlying -source abstractions. - -

-  function streamifyWebSocket(url, protocol) {
-    const ws = new WebSocket(url, protocols);
-    ws.binaryType = "arraybuffer";
-
-    return {
-      readable: new ReadableStream(new WebSocketSource(ws)),
-      writable: new WritableStream(new WebSocketSink(ws))
-    };
+readable property containing a readable stream and the writable property
+containing a writable stream, where both streams wrap the same underlying web socket resource. In
+essence, this combines [[#example-rs-push-no-backpressure]] and [[#example-ws-no-backpressure]].
+
+While doing so, it illustrates how you can use JavaScript classes to create reusable underlying
+sink and underlying source abstractions.
+
+
+function streamifyWebSocket(url, protocol) {
+  const ws = new WebSocket(url, protocols);
+  ws.binaryType = "arraybuffer";
+
+  return {
+    readable: new ReadableStream(new WebSocketSource(ws)),
+    writable: new WritableStream(new WebSocketSink(ws))
+  };
+}
+
+class WebSocketSource {
+  constructor(ws) {
+    this._ws = ws;
   }
 
-  class WebSocketSource {
-    constructor(ws) {
-      this._ws = ws;
-    }
-
-    start(controller) {
-      this._ws.onmessage = event => controller.enqueue(event.data);
-      this._ws.onclose = () => controller.close();
+  start(controller) {
+    this._ws.onmessage = event => controller.enqueue(event.data);
+    this._ws.onclose = () => controller.close();
 
-      this._ws.addEventListener("error", () => {
-        controller.error(new Error("The WebSocket errored!"));
-      });
-    }
+    this._ws.addEventListener("error", () => {
+      controller.error(new Error("The WebSocket errored!"));
+    });
+  }
 
-    cancel() {
-      this._ws.close();
-    }
+  cancel() {
+    this._ws.close();
   }
+}
 
-  class WebSocketSink {
-    constructor(ws) {
-      this._ws = ws;
-    }
+class WebSocketSink {
+  constructor(ws) {
+    this._ws = ws;
+  }
 
-    start(controller) {
-      this._ws.onclose = () => controller.error(new Error("The server closed the connection unexpectedly!"));
-      this._ws.addEventListener("error", () => {
-        controller.error(new Error("The WebSocket errored!"));
-        this._ws.onclose = null;
-      });
+  start(controller) {
+    this._ws.onclose = () => controller.error(new Error("The server closed the connection unexpectedly!"));
+    this._ws.addEventListener("error", () => {
+      controller.error(new Error("The WebSocket errored!"));
+      this._ws.onclose = null;
+    });
 
-      return new Promise(resolve => this._ws.onopen = resolve);
-    }
+    return new Promise(resolve => this._ws.onopen = resolve);
+  }
 
-    write(chunk) {
-      this._ws.send(chunk);
-    }
+  write(chunk) {
+    this._ws.send(chunk);
+  }
 
-    close() {
-      return this._closeWS(1000);
-    }
+  close() {
+    return this._closeWS(1000);
+  }
 
-    abort(reason) {
-      return this._closeWS(4000, reason && reason.message);
-    }
+  abort(reason) {
+    return this._closeWS(4000, reason && reason.message);
+  }
 
-    _closeWS(code, reasonString) {
-      return new Promise((resolve, reject) => {
-        this._ws.onclose = e => {
-          if (e.wasClean) {
-            resolve();
-          } else {
-            reject(new Error("The connection was not closed cleanly"));
-          }
-        };
-        this._ws.close(code, reasonString);
-      });
-    }
+  _closeWS(code, reasonString) {
+    return new Promise((resolve, reject) => {
+      this._ws.onclose = e => {
+        if (e.wasClean) {
+          resolve();
+        } else {
+          reject(new Error("The connection was not closed cleanly"));
+        }
+      };
+      this._ws.close(code, reasonString);
+    });
   }
-</code></pre>
+}
+
 
-We can then use the objects created by this function to communicate with a remote web socket, using the standard stream
-APIs:
+We can then use the objects created by this function to communicate with a remote web socket, using
+the standard stream APIs:
 
-

-  const streamyWS = streamifyWebSocket("wss://example.com:443/", "protocol");
-  const writer = streamyWS.writable.getWriter();
-  const reader = streamyWS.readable.getReader();
+
+const streamyWS = streamifyWebSocket("wss://example.com:443/", "protocol");
+const writer = streamyWS.writable.getWriter();
+const reader = streamyWS.readable.getReader();
 
-  writer.write("Hello");
-  writer.write("web socket!");
+writer.write("Hello");
+writer.write("web socket!");
 
-  reader.read().then(({ value, done }) => {
-    console.log("The web socket says: ", value);
-  });
-</code></pre>
+reader.read().then(({ value, done }) => {
+  console.log("The web socket says: ", value);
+});
+
 
-Note how in this setup canceling the readable side will implicitly close the writable side,
-and similarly, closing or aborting the writable side will implicitly close the readable side.
+Note how in this setup canceling the readable side will implicitly close the
+writable side, and similarly, closing or aborting the writable side will
+implicitly close the readable side.
 
-

See the earlier note about this style of wrapping web -sockets into streams.

+

See the earlier note about this +style of wrapping web sockets into streams.

A transform stream that replaces template tags

-It's often useful to substitute tags with variables on a stream of data, where the parts that need to be replaced are -small compared to the overall data size. This example presents a simple way to do that. It maps strings to strings, -transforming a template like "Time: \{{time}} Message: \{{message}}" to "Time: 15:36 Message: -hello" assuming that { time: "15:36", message: "hello" } was passed in the -substitutions parameter to LipFuzzTransformer. - -This example also demonstrates one way to deal with a situation where a chunk contains partial data that cannot be -transformed until more data is received. In this case, a partial template tag will be accumulated in the -partialChunk instance variable until either the end of the tag is found or the end of the stream is -reached. - -

-  class LipFuzzTransformer {
-    constructor(substitutions) {
-      this.substitutions = substitutions;
-      this.partialChunk = "";
-      this.lastIndex = undefined;
-    }
+It's often useful to substitute tags with variables on a stream of data, where the parts that need
+to be replaced are small compared to the overall data size. This example presents a simple way to
+do that. It maps strings to strings, transforming a template like "Time: \{{time}} Message:
+\{{message}}" to "Time: 15:36 Message: hello" assuming that { time:
+"15:36", message: "hello" } was passed in the substitutions parameter to
+LipFuzzTransformer.
+
+This example also demonstrates one way to deal with a situation where a chunk contains partial data
+that cannot be transformed until more data is received. In this case, a partial template tag will
+be accumulated in the partialChunk property until either the end of the tag is found or
+the end of the stream is reached.
+
+
+class LipFuzzTransformer {
+  constructor(substitutions) {
+    this.substitutions = substitutions;
+    this.partialChunk = "";
+    this.lastIndex = undefined;
+  }
 
-    transform(chunk, controller) {
-      chunk = this.partialChunk + chunk;
-      this.partialChunk = "";
-      // lastIndex is the index of the first character after the last substitution.
-      this.lastIndex = 0;
-      chunk = chunk.replace(/\{\{([a-zA-Z0-9_-]+)\}\}/g, this.replaceTag.bind(this));
-      // Regular expression for an incomplete template at the end of a string.
-      const partialAtEndRegexp = /\{(\{([a-zA-Z0-9_-]+(\})?)?)?$/g;
-      // Avoid looking at any characters that have already been substituted.
-      partialAtEndRegexp.lastIndex = this.lastIndex;
-      this.lastIndex = undefined;
-      const match = partialAtEndRegexp.exec(chunk);
-      if (match) {
-        this.partialChunk = chunk.substring(match.index);
-        chunk = chunk.substring(0, match.index);
-      }
-      controller.enqueue(chunk);
+  transform(chunk, controller) {
+    chunk = this.partialChunk + chunk;
+    this.partialChunk = "";
+    // lastIndex is the index of the first character after the last substitution.
+    this.lastIndex = 0;
+    chunk = chunk.replace(/\{\{([a-zA-Z0-9_-]+)\}\}/g, this.replaceTag.bind(this));
+    // Regular expression for an incomplete template at the end of a string.
+    const partialAtEndRegexp = /\{(\{([a-zA-Z0-9_-]+(\})?)?)?$/g;
+    // Avoid looking at any characters that have already been substituted.
+    partialAtEndRegexp.lastIndex = this.lastIndex;
+    this.lastIndex = undefined;
+    const match = partialAtEndRegexp.exec(chunk);
+    if (match) {
+      this.partialChunk = chunk.substring(match.index);
+      chunk = chunk.substring(0, match.index);
     }
+    controller.enqueue(chunk);
+  }
 
-    flush(controller) {
-      if (this.partialChunk.length > 0) {
-        controller.enqueue(this.partialChunk);
-      }
+  flush(controller) {
+    if (this.partialChunk.length > 0) {
+      controller.enqueue(this.partialChunk);
     }
+  }
 
-    replaceTag(match, p1, offset) {
-      let replacement = this.substitutions[p1];
-      if (replacement === undefined) {
-        replacement = "";
-      }
-      this.lastIndex = offset + replacement.length;
-      return replacement;
+  replaceTag(match, p1, offset) {
+    let replacement = this.substitutions[p1];
+    if (replacement === undefined) {
+      replacement = "";
     }
+    this.lastIndex = offset + replacement.length;
+    return replacement;
   }
-</code></pre>
+}
+
 
-In this case we define the transformer to be passed to the {{TransformStream}} constructor as a class. This is
-useful when there is instance data to track.
+In this case we define the [=transformer=] to be passed to the {{TransformStream}} constructor as a
+class. This is useful when there is instance data to track.
 
 The class would be used in code like:
 
-

-  const data = { userName, displayName, icon, date };
-  const ts = new TransformStream(new LipFuzzTransformer(data));
-
-  fetchEvent.respondWith(
-    fetch(fetchEvent.request.url).then(response => {
-      const transformedBody = response.body
-        // Decode the binary-encoded response to string
-        .pipeThrough(new TextDecoderStream())
-        // Apply the LipFuzzTransformer
-        .pipeThrough(ts)
-        // Encode the transformed string
-        .pipeThrough(new TextEncoderStream());
-      return new Response(transformedBody);
-    })
-  );
-
- -
For simplicity, LipFuzzTransformer performs unescaped text substitutions. In real -applications, a template system that performs context-aware escaping is good practice for security and robustness.
+ +const data = { userName, displayName, icon, date }; +const ts = new TransformStream(new LipFuzzTransformer(data)); + +fetchEvent.respondWith( + fetch(fetchEvent.request.url).then(response => { + const transformedBody = response.body + // Decode the binary-encoded response to string + .pipeThrough(new TextDecoderStream()) + // Apply the LipFuzzTransformer + .pipeThrough(ts) + // Encode the transformed string + .pipeThrough(new TextEncoderStream()); + return new Response(transformedBody); + }) +); + + +

For simplicity, LipFuzzTransformer performs unescaped text +substitutions. In real applications, a template system that performs context-aware escaping is good +practice for security and robustness.

A transform stream created from a sync mapper function

-The following function allows creating new {{TransformStream}} instances from synchronous "mapper" functions, of the -type you would normally pass to {{Array.prototype/map|Array.prototype.map}}. It demonstrates that the API is concise -even for trivial transforms. +The following function allows creating new {{TransformStream}} instances from synchronous "mapper" +functions, of the type you would normally pass to {{Array.prototype/map|Array.prototype.map}}. It +demonstrates that the API is concise even for trivial transforms. -

-  function mapperTransformStream(mapperFunction) {
-    return new TransformStream({
-      transform(chunk, controller) {
-        controller.enqueue(mapperFunction(chunk));
-      }
-    });
-  }
-
+ +function mapperTransformStream(mapperFunction) { + return new TransformStream({ + transform(chunk, controller) { + controller.enqueue(mapperFunction(chunk)); + } + }); +} + This function can then be used to create a {{TransformStream}} that uppercases all its inputs: -

-  const ts = mapperTransformStream(chunk => chunk.toUpperCase());
-  const writer = ts.writable.getWriter();
-  const reader = ts.readable.getReader();
+
+const ts = mapperTransformStream(chunk => chunk.toUpperCase());
+const writer = ts.writable.getWriter();
+const reader = ts.readable.getReader();
 
-  writer.write("No need to shout");
+writer.write("No need to shout");
 
-  // Logs "NO NEED TO SHOUT":
-  reader.read().then(({ value }) => console.log(value));
-</code></pre>
+// Logs "NO NEED TO SHOUT":
+reader.read().then(({ value }) => console.log(value));
+
 
-Although a synchronous transform never causes backpressure itself, it will only transform chunks as long as there is no
-backpressure, so resources will not be wasted.
+Although a synchronous transform never causes backpressure itself, it will only transform chunks as
+long as there is no backpressure, so resources will not be wasted.
 
 Exceptions error the stream in a natural way:
 
-

-  const ts = mapperTransformStream(chunk => JSON.parse(chunk));
-  const writer = ts.writable.getWriter();
-  const reader = ts.readable.getReader();
+
+const ts = mapperTransformStream(chunk => JSON.parse(chunk));
+const writer = ts.writable.getWriter();
+const reader = ts.readable.getReader();
 
-  writer.write("[1, ");
-
-  // Logs a SyntaxError, twice:
-  reader.read().catch(e => console.error(e));
-  writer.write("{}").catch(e => console.error(e));
-</code></pre>
-
-<h2 id="conventions" class="no-num">Conventions</h2>
-
-This specification depends on the Infra Standard. [[!INFRA]]
+writer.write("[1, ");
 
-This specification uses algorithm conventions very similar to those of [[!ECMASCRIPT]], whose rules should be used to
-interpret it (apart from the exceptions enumerated below). In particular, the objects specified here should be treated
-as <a href="https://tc39.github.io/ecma262/#sec-ecmascript-standard-built-in-objects">built-in objects</a>. For example,
-their <code>name</code> and <code>length</code> properties are derived as described by that specification, as are the
-default property descriptor values and the treatment of missing, <emu-val>undefined</emu-val>, or surplus arguments.
-
-We also depart from the [[!ECMASCRIPT]] conventions in the following ways, mostly for brevity. It is hoped (and vaguely
-planned) that the conventions of ECMAScript itself will evolve in these ways.
-
-<ul>
-  <li> We prefix section headings with <code>new</code> to indicate they are defining constructors; when doing so, we
-  assume that NewTarget will be checked before the algorithm starts.
-  <li> We use the default argument notation <code>= {}</code> in a couple of cases, meaning that before the algorithm
-    starts, <emu-val>undefined</emu-val> (including the implicit <emu-val>undefined</emu-val> when no argument is
-    provided) is instead treated as a new object created as if by ObjectCreate(%ObjectPrototype%). (This object may then
-    be destructured, if combined with the below destructuring convention.)
-  <li> We use destructuring notation in function and method declarations, and assume that <a
-    abstract-op>DestructuringAssignmentEvaluation</a> was performed appropriately before the algorithm starts.
-  <li> We use "<emu-val>this</emu-val>" instead of "<emu-val>this</emu-val> value".
-</ul>
-
-It's also worth noting that, as in [[!ECMASCRIPT]], all numbers are represented as double-precision floating point
-values, and all arithmetic operations performed on them must be done in the standard way for such values.
+// Logs a SyntaxError, twice:
+reader.read().catch(e => console.error(e));
+writer.write("{}").catch(e => console.error(e));
+
 
 

Acknowledgments

diff --git a/local-watch.js b/local-watch.js deleted file mode 100644 index 8c8924789..000000000 --- a/local-watch.js +++ /dev/null @@ -1,83 +0,0 @@ -'use strict'; -const fs = require('fs'); -const childProcess = require('child_process'); -const promiseDebounce = require('promise-debounce'); -const emuAlgify = require('emu-algify'); - -const INPUT = 'index.bs'; - -let fsWatcher; - -const build = promiseDebounce(() => { - log('Building...'); - - try { - childProcess.execSync( - `bikeshed spec ${INPUT} index.html.postbs --md-Text-Macro="SNAPSHOT-LINK "`, - { encoding: 'utf-8', stdio: 'inherit' } - ); - log('(bikeshed done)'); - } catch (e) { - error('Error executing bikeshed:\n'); - console.error(e.stdout); - } - - const input = fs.readFileSync('index.html.postbs', { encoding: 'utf-8' }); - fs.unlinkSync('index.html.postbs'); - - return emuAlgify(input, { throwingIndicators: true }) - .then(output => { - fs.writeFileSync('index.html.new', output); - fs.renameSync('index.html.new', 'index.html'); - log('Build complete'); - }) - .catch(err => { - error('Error executing ecmarkupify:\n'); - console.error(err); - } - ); -}); - -function onChange(eventType, filename) { - log(`Saw ${eventType} event with filename '${filename}'`); - // Restart the watch in case the file has been renamed or deleted. This fixes an issue where the file stopped being - // watched when a different branch was checked out. - tryWatch(); -} - -// If index.bs exists, start watching it and run a build. Otherwise retry with a truncated exponential delay until it -// starts to exist. -function tryWatch(delay) { - if (fsWatcher !== undefined) { - fsWatcher.close(); - fsWatcher = undefined; - } - try { - fsWatcher = fs.watch(INPUT, onChange); - build(); - } catch (e) { - if (e.code === 'ENOENT') { - log(`${INPUT} not there right now. Waiting a bit.`); - if (delay === undefined) { - delay = 100; - } - delay *= 2; - if (delay > 20000) { - delay = 20000; - } - setTimeout(tryWatch, delay, delay); - } else { - throw e; - } - } -} - -tryWatch(); - -function log(s) { - console.log(`[${(new Date()).toISOString()}] ${s}`); -} - -function error(s) { - console.error(`[${(new Date()).toISOString()}] ${s}`); -} diff --git a/package.json b/package.json deleted file mode 100644 index b49f7348f..000000000 --- a/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "private": true, - "description": "Build process supporting stuff for WHATWG Streams Standard document", - "scripts": { - "local-watch": "node ./local-watch.js" - }, - "devDependencies": { - "emu-algify": "^2.2.0", - "promise-debounce": "^1.0.0" - } -} diff --git a/reference-implementation/.eslintignore b/reference-implementation/.eslintignore index 46e585972..d9729fadd 100644 --- a/reference-implementation/.eslintignore +++ b/reference-implementation/.eslintignore @@ -1,2 +1,3 @@ web-platform-tests/ coverage/ +generated/ diff --git a/reference-implementation/.eslintrc.json b/reference-implementation/.eslintrc.json index f3486a456..b74da9cf5 100644 --- a/reference-implementation/.eslintrc.json +++ b/reference-implementation/.eslintrc.json @@ -3,19 +3,15 @@ "env": { "node": true, "browser": true, - "es6": true + "es2020": true }, "parserOptions": { - "ecmaVersion": 2018 + "ecmaVersion": 2020 }, "globals": { - "ReadableStream": false, - "WritableStream": false, - "TransformStream": false, - "ByteLengthQueuingStrategy": false, - "CountQueuingStrategy": false, "GCController": false, - "gc": false + "gc": false, + "globalThis": false }, "rules": { // Possible errors diff --git a/reference-implementation/.gitignore b/reference-implementation/.gitignore index 2386693ff..c84396803 100644 --- a/reference-implementation/.gitignore +++ b/reference-implementation/.gitignore @@ -4,3 +4,4 @@ package-lock.json .nyc_output/ coverage/ +generated/ diff --git a/reference-implementation/compile-idl.js b/reference-implementation/compile-idl.js new file mode 100644 index 000000000..f5c81d480 --- /dev/null +++ b/reference-implementation/compile-idl.js @@ -0,0 +1,22 @@ +'use strict'; +/* eslint-disable no-console, no-process-exit */ +const { mkdirSync } = require('fs'); +const path = require('path'); +const Transformer = require('webidl2js'); + +const input = path.resolve(__dirname, './lib'); +const output = path.resolve(__dirname, './generated'); + +mkdirSync(output, { recursive: true }); + +const transformer = new Transformer({ + implSuffix: '-impl', + suppressErrors: true // until https://github.com/jsdom/webidl2js/pull/123 lands +}); + +transformer.addSource(input, input); +transformer.generate(output) + .catch(err => { + console.error(err.stack); + process.exit(1); + }); diff --git a/reference-implementation/lib/ByteLengthQueuingStrategy-impl.js b/reference-implementation/lib/ByteLengthQueuingStrategy-impl.js new file mode 100644 index 000000000..ebc23938c --- /dev/null +++ b/reference-implementation/lib/ByteLengthQueuingStrategy-impl.js @@ -0,0 +1,26 @@ +'use strict'; + +exports.implementation = class ByteLengthQueuingStrategyImpl { + constructor(globalObject, [{ highWaterMark }]) { + this._globalObject = globalObject; + this.highWaterMark = highWaterMark; + } + + get size() { + initializeSizeFunction(this._globalObject); + return sizeFunctionWeakMap.get(this._globalObject); + } +}; + +const sizeFunctionWeakMap = new WeakMap(); +function initializeSizeFunction(globalObject) { + if (sizeFunctionWeakMap.has(globalObject)) { + return; + } + + // We need to set the 'name' property: + // eslint-disable-next-line prefer-arrow-callback + sizeFunctionWeakMap.set(globalObject, function size(chunk) { + return chunk.byteLength; + }); +} diff --git a/reference-implementation/lib/ByteLengthQueuingStrategy.webidl b/reference-implementation/lib/ByteLengthQueuingStrategy.webidl new file mode 100644 index 000000000..48b90bd09 --- /dev/null +++ b/reference-implementation/lib/ByteLengthQueuingStrategy.webidl @@ -0,0 +1,7 @@ +[Exposed=(Window,Worker,Worklet)] +interface ByteLengthQueuingStrategy { + constructor(QueuingStrategyInit init); + + readonly attribute unrestricted double highWaterMark; + readonly attribute Function size; +}; diff --git a/reference-implementation/lib/CountQueuingStrategy-impl.js b/reference-implementation/lib/CountQueuingStrategy-impl.js new file mode 100644 index 000000000..719433e95 --- /dev/null +++ b/reference-implementation/lib/CountQueuingStrategy-impl.js @@ -0,0 +1,26 @@ +'use strict'; + +exports.implementation = class CountQueuingStrategyImpl { + constructor(globalObject, [{ highWaterMark }]) { + this._globalObject = globalObject; + this.highWaterMark = highWaterMark; + } + + get size() { + initializeSizeFunction(this._globalObject); + return sizeFunctionWeakMap.get(this._globalObject); + } +}; + +const sizeFunctionWeakMap = new WeakMap(); +function initializeSizeFunction(globalObject) { + if (sizeFunctionWeakMap.has(globalObject)) { + return; + } + + // We need to set the 'name' property: + // eslint-disable-next-line prefer-arrow-callback + sizeFunctionWeakMap.set(globalObject, function size() { + return 1; + }); +} diff --git a/reference-implementation/lib/CountQueuingStrategy.webidl b/reference-implementation/lib/CountQueuingStrategy.webidl new file mode 100644 index 000000000..13bb1fd07 --- /dev/null +++ b/reference-implementation/lib/CountQueuingStrategy.webidl @@ -0,0 +1,7 @@ +[Exposed=(Window,Worker,Worklet)] +interface CountQueuingStrategy { + constructor(QueuingStrategyInit init); + + readonly attribute unrestricted double highWaterMark; + readonly attribute Function size; +}; diff --git a/reference-implementation/lib/QueuingStrategy.webidl b/reference-implementation/lib/QueuingStrategy.webidl new file mode 100644 index 000000000..aac307292 --- /dev/null +++ b/reference-implementation/lib/QueuingStrategy.webidl @@ -0,0 +1,6 @@ +dictionary QueuingStrategy { + unrestricted double highWaterMark; + QueuingStrategySize size; +}; + +callback QueuingStrategySize = unrestricted double (optional any chunk); diff --git a/reference-implementation/lib/QueuingStrategyInit.webidl b/reference-implementation/lib/QueuingStrategyInit.webidl new file mode 100644 index 000000000..17b795cd9 --- /dev/null +++ b/reference-implementation/lib/QueuingStrategyInit.webidl @@ -0,0 +1,3 @@ +dictionary QueuingStrategyInit { + required unrestricted double highWaterMark; +}; diff --git a/reference-implementation/lib/ReadableByteStreamController-impl.js b/reference-implementation/lib/ReadableByteStreamController-impl.js new file mode 100644 index 000000000..3a6c52db8 --- /dev/null +++ b/reference-implementation/lib/ReadableByteStreamController-impl.js @@ -0,0 +1,127 @@ +'use strict'; +const assert = require('assert'); + +const { promiseResolvedWith, promiseRejectedWith } = require('./helpers/webidl.js'); +const { CancelSteps, PullSteps } = require('./abstract-ops/internal-methods.js'); +const { ResetQueue } = require('./abstract-ops/queue-with-sizes.js'); +const aos = require('./abstract-ops/readable-streams.js'); + +const ReadableStreamBYOBRequest = require('../generated/ReadableStreamBYOBRequest.js'); + +exports.implementation = class ReadableByteStreamControllerImpl { + get byobRequest() { + if (this._byobRequest === null && this._pendingPullIntos.length > 0) { + const firstDescriptor = this._pendingPullIntos[0]; + const view = new Uint8Array(firstDescriptor.buffer, + firstDescriptor.byteOffset + firstDescriptor.bytesFilled, + firstDescriptor.byteLength - firstDescriptor.bytesFilled); + + const byobRequest = ReadableStreamBYOBRequest.new(globalThis); + byobRequest._controller = this; + byobRequest._view = view; + this._byobRequest = byobRequest; + } + + return this._byobRequest; + } + + get desiredSize() { + return aos.ReadableByteStreamControllerGetDesiredSize(this); + } + + close() { + if (this._closeRequested === true) { + throw new TypeError('The stream has already been closed; do not close it again!'); + } + + const state = this._controlledReadableStream._state; + if (state !== 'readable') { + throw new TypeError(`The stream (in ${state} state) is not in the readable state and cannot be closed`); + } + + aos.ReadableByteStreamControllerClose(this); + } + + enqueue(chunk) { + if (chunk.byteLength === 0) { + throw new TypeError('chunk must have non-zero byteLength'); + } + if (chunk.buffer.byteLength === 0) { + throw new TypeError('chunk\'s buffer must have non-zero byteLength'); + } + + if (this._closeRequested === true) { + throw new TypeError('stream is closed or draining'); + } + + const state = this._controlledReadableStream._state; + if (state !== 'readable') { + throw new TypeError(`The stream (in ${state} state) is not in the readable state and cannot be enqueued to`); + } + + aos.ReadableByteStreamControllerEnqueue(this, chunk); + } + + error(e) { + aos.ReadableByteStreamControllerError(this, e); + } + + [CancelSteps](reason) { + if (this._pendingPullIntos.length > 0) { + const firstDescriptor = this._pendingPullIntos[0]; + firstDescriptor.bytesFilled = 0; + } + + ResetQueue(this); + + const result = this._cancelAlgorithm(reason); + aos.ReadableByteStreamControllerClearAlgorithms(this); + return result; + } + + [PullSteps]() { + const stream = this._controlledReadableStream; + assert(aos.ReadableStreamHasDefaultReader(stream) === true); + + if (this._queueTotalSize > 0) { + assert(aos.ReadableStreamGetNumReadRequests(stream) === 0); + + const entry = this._queue.shift(); + this._queueTotalSize -= entry.byteLength; + + aos.ReadableByteStreamControllerHandleQueueDrain(this); + + const view = new Uint8Array(entry.buffer, entry.byteOffset, entry.byteLength); + + return promiseResolvedWith(aos.ReadableStreamCreateReadResult(view, false, stream._reader._forAuthorCode)); + } + + const autoAllocateChunkSize = this._autoAllocateChunkSize; + if (autoAllocateChunkSize !== undefined) { + let buffer; + try { + buffer = new ArrayBuffer(autoAllocateChunkSize); + } catch (bufferE) { + return promiseRejectedWith(bufferE); + } + + const pullIntoDescriptor = { + buffer, + byteOffset: 0, + byteLength: autoAllocateChunkSize, + bytesFilled: 0, + elementSize: 1, + ctor: Uint8Array, + readerType: 'default' + }; + + this._pendingPullIntos.push(pullIntoDescriptor); + } + + const promise = aos.ReadableStreamAddReadRequest(stream); + + aos.ReadableByteStreamControllerCallPullIfNeeded(this); + + return promise; + } +}; diff --git a/reference-implementation/lib/ReadableByteStreamController.webidl b/reference-implementation/lib/ReadableByteStreamController.webidl new file mode 100644 index 000000000..9e278fc1e --- /dev/null +++ b/reference-implementation/lib/ReadableByteStreamController.webidl @@ -0,0 +1,9 @@ +[Exposed=(Window,Worker,Worklet)] +interface ReadableByteStreamController { + readonly attribute ReadableStreamBYOBRequest? byobRequest; + readonly attribute unrestricted double? desiredSize; + + void close(); + void enqueue(ArrayBufferView chunk); + void error(optional any e); +}; diff --git a/reference-implementation/lib/ReadableStream-impl.js b/reference-implementation/lib/ReadableStream-impl.js new file mode 100644 index 000000000..11d6481c0 --- /dev/null +++ b/reference-implementation/lib/ReadableStream-impl.js @@ -0,0 +1,180 @@ +'use strict'; +const assert = require('assert'); + +const { promiseResolvedWith, promiseRejectedWith, setPromiseIsHandledToTrue, transformPromiseWith } = + require('./helpers/webidl.js'); +const { typeIsObject } = require('./helpers/miscellaneous.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'); + +const idlUtils = require('../generated/utils.js'); +const UnderlyingSource = require('../generated/UnderlyingSource.js'); + +exports.implementation = class ReadableStreamImpl { + constructor(globalObject, [underlyingSource, strategy]) { + if (underlyingSource === undefined) { + underlyingSource = null; + } + const underlyingSourceDict = UnderlyingSource.convert(underlyingSource); + + aos.InitializeReadableStream(this); + + if (underlyingSourceDict.type === 'bytes') { + if ('size' in strategy) { + throw new RangeError('The strategy for a byte stream cannot have a size function'); + } + + const highWaterMark = ExtractHighWaterMark(strategy, 0); + aos.SetUpReadableByteStreamControllerFromUnderlyingSource( + this, underlyingSource, underlyingSourceDict, highWaterMark + ); + } else { + assert(!('type' in underlyingSourceDict)); + const sizeAlgorithm = ExtractSizeAlgorithm(strategy); + const highWaterMark = ExtractHighWaterMark(strategy, 1); + aos.SetUpReadableStreamDefaultControllerFromUnderlyingSource( + this, underlyingSource, underlyingSourceDict, highWaterMark, sizeAlgorithm + ); + } + } + + get locked() { + return aos.IsReadableStreamLocked(this); + } + + cancel(reason) { + if (aos.IsReadableStreamLocked(this) === true) { + return promiseRejectedWith(new TypeError('Cannot cancel a stream that already has a reader')); + } + + return aos.ReadableStreamCancel(this, reason); + } + + getReader(options) { + if (!('mode' in options)) { + return aos.AcquireReadableStreamDefaultReader(this, true); + } + + assert(options.mode === 'byob'); + return aos.AcquireReadableStreamBYOBReader(this, true); + } + + pipeThrough(transform, options) { + // Type checking here is needed until https://github.com/jsdom/webidl2js/issues/81 is fixed. + if ('signal' in options) { + if (!isAbortSignal(options.signal)) { + throw new TypeError('Invalid signal argument'); + } + } + + if (aos.IsReadableStreamLocked(this) === true) { + throw new TypeError('ReadableStream.prototype.pipeThrough cannot be used on a locked ReadableStream'); + } + if (wsAOs.IsWritableStreamLocked(transform.writable) === true) { + throw new TypeError('ReadableStream.prototype.pipeThrough cannot be used on a locked WritableStream'); + } + + const promise = aos.ReadableStreamPipeTo( + this, transform.writable, options.preventClose, options.preventAbort, options.preventCancel, options.signal + ); + + setPromiseIsHandledToTrue(promise); + + return transform.readable; + } + + pipeTo(destination, options) { + // Type checking here is needed until https://github.com/jsdom/webidl2js/issues/81 is fixed. + if ('signal' in options) { + if (!isAbortSignal(options.signal)) { + return promiseRejectedWith(new TypeError('Invalid signal argument')); + } + } + + if (aos.IsReadableStreamLocked(this) === true) { + return promiseRejectedWith( + new TypeError('ReadableStream.prototype.pipeTo cannot be used on a locked ReadableStream') + ); + } + if (wsAOs.IsWritableStreamLocked(destination) === true) { + return promiseRejectedWith( + new TypeError('ReadableStream.prototype.pipeTo cannot be used on a locked WritableStream') + ); + } + + return aos.ReadableStreamPipeTo( + this, destination, options.preventClose, options.preventAbort, options.preventCancel, options.signal + ); + } + + tee() { + // Conversion here is only needed until https://github.com/jsdom/webidl2js/pull/108 gets merged. + return aos.ReadableStreamTee(this, false).map(idlUtils.wrapperForImpl); + } + + [idlUtils.asyncIteratorInit](iterator, [options]) { + iterator._reader = aos.AcquireReadableStreamDefaultReader(this); + iterator._preventCancel = options.preventCancel; + } + + [idlUtils.asyncIteratorNext](iterator) { + const reader = iterator._reader; + if (reader._ownerReadableStream === undefined) { + return promiseRejectedWith( + new TypeError('Cannot get the next iteration result once the reader has been released') + ); + } + + return transformPromiseWith( + aos.ReadableStreamDefaultReaderRead(reader), + result => { + assert(typeIsObject(result)); + + const { done } = result; + assert(typeof done === 'boolean'); + + if (done === true) { + aos.ReadableStreamReaderGenericRelease(reader); + return idlUtils.asyncIteratorEOI; + } + + const { value } = result; + return value; + }, + reason => { + aos.ReadableStreamReaderGenericRelease(reader); + throw reason; + } + ); + } + + [idlUtils.asyncIteratorReturn](iterator, arg) { + const reader = iterator._reader; + if (reader._ownerReadableStream === undefined) { + return promiseResolvedWith(undefined); + } + + assert(reader._readRequests.length === 0); + + if (iterator._preventCancel === false) { + const result = aos.ReadableStreamReaderGenericCancel(reader, arg); + aos.ReadableStreamReaderGenericRelease(reader); + return result; + } + + aos.ReadableStreamReaderGenericRelease(reader); + return promiseResolvedWith(undefined); + } +}; + +// See pipeTo()/pipeThrough() for why this is needed. +const abortedGetter = Object.getOwnPropertyDescriptor(AbortSignal.prototype, 'aborted').get; +function isAbortSignal(v) { + try { + abortedGetter.call(v); + return true; + } catch { + return false; + } +} diff --git a/reference-implementation/lib/ReadableStream.webidl b/reference-implementation/lib/ReadableStream.webidl new file mode 100644 index 000000000..54d070f82 --- /dev/null +++ b/reference-implementation/lib/ReadableStream.webidl @@ -0,0 +1,38 @@ +[Exposed=(Window,Worker,Worklet)] +interface ReadableStream { + constructor(optional object underlyingSource, optional QueuingStrategy strategy = {}); + + readonly attribute boolean locked; + + Promise cancel(optional any reason); + ReadableStreamReader getReader(optional ReadableStreamGetReaderOptions options = {}); + ReadableStream pipeThrough(ReadableWritablePair transform, optional StreamPipeOptions options = {}); + Promise pipeTo(WritableStream destination, optional StreamPipeOptions options = {}); + sequence tee(); + + [WebIDL2JSHasReturnSteps] async iterable(optional ReadableStreamIteratorOptions options = {}); +}; + +typedef (ReadableStreamDefaultReader or ReadableStreamBYOBReader) ReadableStreamReader; + +enum ReadableStreamReaderMode { "byob" }; + +dictionary ReadableStreamGetReaderOptions { + ReadableStreamReaderMode mode; +}; + +dictionary ReadableStreamIteratorOptions { + boolean preventCancel = false; +}; + +dictionary ReadableWritablePair { + required ReadableStream readable; + required WritableStream writable; +}; + +dictionary StreamPipeOptions { + boolean preventClose = false; + boolean preventAbort = false; + boolean preventCancel = false; + AbortSignal signal; +}; diff --git a/reference-implementation/lib/ReadableStreamBYOBReader-impl.js b/reference-implementation/lib/ReadableStreamBYOBReader-impl.js new file mode 100644 index 000000000..dd30fd5fa --- /dev/null +++ b/reference-implementation/lib/ReadableStreamBYOBReader-impl.js @@ -0,0 +1,53 @@ +'use strict'; + +const { promiseRejectedWith } = require('./helpers/webidl.js'); +const aos = require('./abstract-ops/readable-streams.js'); + +exports.implementation = class ReadableStreamBYOBReaderImpl { + constructor(globalObject, [stream]) { + aos.SetUpReadableStreamBYOBReader(this, stream); + } + + get closed() { + return this._closedPromise; + } + + cancel(reason) { + if (this._ownerReadableStream === undefined) { + return promiseRejectedWith(readerLockException('cancel')); + } + + return aos.ReadableStreamReaderGenericCancel(this, reason); + } + + read(view) { + if (view.byteLength === 0) { + return promiseRejectedWith(new TypeError('view must have non-zero byteLength')); + } + if (view.buffer.byteLength === 0) { + return promiseRejectedWith(new TypeError('view\'s buffer must have non-zero byteLength')); + } + + if (this._ownerReadableStream === undefined) { + return promiseRejectedWith(readerLockException('read')); + } + + return aos.ReadableStreamBYOBReaderRead(this, view); + } + + releaseLock() { + if (this._ownerReadableStream === undefined) { + return; + } + + if (this._readIntoRequests.length > 0) { + throw new TypeError('Tried to release a reader lock when that reader has pending read() calls un-settled'); + } + + aos.ReadableStreamReaderGenericRelease(this); + } +}; + +function readerLockException(name) { + return new TypeError('Cannot ' + name + ' a stream using a released reader'); +} diff --git a/reference-implementation/lib/ReadableStreamBYOBReader.webidl b/reference-implementation/lib/ReadableStreamBYOBReader.webidl new file mode 100644 index 000000000..8ac1b05a5 --- /dev/null +++ b/reference-implementation/lib/ReadableStreamBYOBReader.webidl @@ -0,0 +1,10 @@ +[Exposed=(Window,Worker,Worklet)] +interface ReadableStreamBYOBReader { + constructor(ReadableStream stream); + + readonly attribute Promise closed; + + Promise cancel(optional any reason); + Promise read(ArrayBufferView view); + void releaseLock(); +}; diff --git a/reference-implementation/lib/ReadableStreamBYOBRequest-impl.js b/reference-implementation/lib/ReadableStreamBYOBRequest-impl.js new file mode 100644 index 000000000..2e737fd9b --- /dev/null +++ b/reference-implementation/lib/ReadableStreamBYOBRequest-impl.js @@ -0,0 +1,41 @@ +'use strict'; +const assert = require('assert'); + +const { IsDetachedBuffer } = require('./abstract-ops/ecmascript.js'); +const aos = require('./abstract-ops/readable-streams.js'); + +exports.implementation = class ReadableStreamBYOBRequestImpl { + get view() { + return this._view; + } + + respond(bytesWritten) { + if (this._controller === undefined) { + throw new TypeError('This BYOB request has been invalidated'); + } + + if (IsDetachedBuffer(this._view.buffer) === true) { + throw new TypeError('The BYOB request\'s buffer has been detached and so cannot be used as a response'); + } + + assert(this._view.byteLength > 0); + assert(this._view.buffer.byteLength > 0); + + aos.ReadableByteStreamControllerRespond(this._controller, bytesWritten); + } + + respondWithNewView(view) { + if (view.byteLength === 0) { + throw new TypeError('chunk must have non-zero byteLength'); + } + if (view.buffer.byteLength === 0) { + throw new TypeError('chunk\'s buffer must have non-zero byteLength'); + } + + if (this._controller === undefined) { + throw new TypeError('This BYOB request has been invalidated'); + } + + aos.ReadableByteStreamControllerRespondWithNewView(this._controller, view); + } +}; diff --git a/reference-implementation/lib/ReadableStreamBYOBRequest.webidl b/reference-implementation/lib/ReadableStreamBYOBRequest.webidl new file mode 100644 index 000000000..0f77734e9 --- /dev/null +++ b/reference-implementation/lib/ReadableStreamBYOBRequest.webidl @@ -0,0 +1,7 @@ +[Exposed=(Window,Worker,Worklet)] +interface ReadableStreamBYOBRequest { + readonly attribute ArrayBufferView? view; + + void respond([EnforceRange] unsigned long long bytesWritten); + void respondWithNewView(ArrayBufferView view); +}; diff --git a/reference-implementation/lib/ReadableStreamDefaultController-impl.js b/reference-implementation/lib/ReadableStreamDefaultController-impl.js new file mode 100644 index 000000000..fdd747e7e --- /dev/null +++ b/reference-implementation/lib/ReadableStreamDefaultController-impl.js @@ -0,0 +1,60 @@ +'use strict'; + +const { promiseResolvedWith } = require('./helpers/webidl.js'); +const { CancelSteps, PullSteps } = require('./abstract-ops/internal-methods.js'); +const { DequeueValue, ResetQueue } = require('./abstract-ops/queue-with-sizes.js'); +const aos = require('./abstract-ops/readable-streams.js'); + +exports.implementation = class ReadableStreamDefaultControllerImpl { + get desiredSize() { + return aos.ReadableStreamDefaultControllerGetDesiredSize(this); + } + + close() { + if (aos.ReadableStreamDefaultControllerCanCloseOrEnqueue(this) === false) { + throw new TypeError('The stream is not in a state that permits close'); + } + + aos.ReadableStreamDefaultControllerClose(this); + } + + enqueue(chunk) { + if (aos.ReadableStreamDefaultControllerCanCloseOrEnqueue(this) === false) { + throw new TypeError('The stream is not in a state that permits enqueue'); + } + + return aos.ReadableStreamDefaultControllerEnqueue(this, chunk); + } + + error(e) { + aos.ReadableStreamDefaultControllerError(this, e); + } + + [CancelSteps](reason) { + ResetQueue(this); + const result = this._cancelAlgorithm(reason); + aos.ReadableStreamDefaultControllerClearAlgorithms(this); + return result; + } + + [PullSteps]() { + const stream = this._controlledReadableStream; + + if (this._queue.length > 0) { + const chunk = DequeueValue(this); + + if (this._closeRequested === true && this._queue.length === 0) { + aos.ReadableStreamDefaultControllerClearAlgorithms(this); + aos.ReadableStreamClose(stream); + } else { + aos.ReadableStreamDefaultControllerCallPullIfNeeded(this); + } + + return promiseResolvedWith(aos.ReadableStreamCreateReadResult(chunk, false, stream._reader._forAuthorCode)); + } + + const pendingPromise = aos.ReadableStreamAddReadRequest(stream); + aos.ReadableStreamDefaultControllerCallPullIfNeeded(this); + return pendingPromise; + } +}; diff --git a/reference-implementation/lib/ReadableStreamDefaultController.webidl b/reference-implementation/lib/ReadableStreamDefaultController.webidl new file mode 100644 index 000000000..23809d0a8 --- /dev/null +++ b/reference-implementation/lib/ReadableStreamDefaultController.webidl @@ -0,0 +1,8 @@ +[Exposed=(Window,Worker,Worklet)] +interface ReadableStreamDefaultController { + readonly attribute unrestricted double? desiredSize; + + void close(); + void enqueue(optional any chunk); + void error(optional any e); +}; diff --git a/reference-implementation/lib/ReadableStreamDefaultReader-impl.js b/reference-implementation/lib/ReadableStreamDefaultReader-impl.js new file mode 100644 index 000000000..e4bb857c5 --- /dev/null +++ b/reference-implementation/lib/ReadableStreamDefaultReader-impl.js @@ -0,0 +1,46 @@ +'use strict'; + +const { promiseRejectedWith } = require('./helpers/webidl.js'); +const aos = require('./abstract-ops/readable-streams.js'); + +exports.implementation = class ReadableStreamDefaultReaderImpl { + constructor(globalObject, [stream]) { + aos.SetUpReadableStreamDefaultReader(this, stream); + } + + get closed() { + return this._closedPromise; + } + + cancel(reason) { + if (this._ownerReadableStream === undefined) { + return promiseRejectedWith(readerLockException('cancel')); + } + + return aos.ReadableStreamReaderGenericCancel(this, reason); + } + + read() { + if (this._ownerReadableStream === undefined) { + return promiseRejectedWith(readerLockException('read from')); + } + + return aos.ReadableStreamDefaultReaderRead(this); + } + + releaseLock() { + if (this._ownerReadableStream === undefined) { + return; + } + + if (this._readRequests.length > 0) { + throw new TypeError('Tried to release a reader lock when that reader has pending read() calls un-settled'); + } + + aos.ReadableStreamReaderGenericRelease(this); + } +}; + +function readerLockException(name) { + return new TypeError('Cannot ' + name + ' a stream using a released reader'); +} diff --git a/reference-implementation/lib/ReadableStreamDefaultReader.webidl b/reference-implementation/lib/ReadableStreamDefaultReader.webidl new file mode 100644 index 000000000..f750cb2f7 --- /dev/null +++ b/reference-implementation/lib/ReadableStreamDefaultReader.webidl @@ -0,0 +1,10 @@ +[Exposed=(Window,Worker,Worklet)] +interface ReadableStreamDefaultReader { + constructor(ReadableStream stream); + + readonly attribute Promise closed; + + Promise cancel(optional any reason); + Promise read(); + void releaseLock(); +}; diff --git a/reference-implementation/lib/TransformStream-impl.js b/reference-implementation/lib/TransformStream-impl.js new file mode 100644 index 000000000..75c3a37d3 --- /dev/null +++ b/reference-implementation/lib/TransformStream-impl.js @@ -0,0 +1,48 @@ +'use strict'; + +const { newPromise, resolvePromise } = require('./helpers/webidl.js'); +const { ExtractHighWaterMark, ExtractSizeAlgorithm } = require('./abstract-ops/queuing-strategy.js'); +const aos = require('./abstract-ops/transform-streams.js'); + +const Transformer = require('../generated/Transformer.js'); + +exports.implementation = class TransformStreamImpl { + constructor(globalObject, [transformer, writableStrategy, readableStrategy]) { + if (transformer === undefined) { + transformer = null; + } + const transformerDict = Transformer.convert(transformer); + if ('readableType' in transformerDict) { + throw new RangeError('Invalid readableType specified'); + } + if ('writableType' in transformerDict) { + throw new RangeError('Invalid writableType specified'); + } + + const readableHighWaterMark = ExtractHighWaterMark(readableStrategy, 0); + const readableSizeAlgorithm = ExtractSizeAlgorithm(readableStrategy); + const writableHighWaterMark = ExtractHighWaterMark(writableStrategy, 1); + const writableSizeAlgorithm = ExtractSizeAlgorithm(writableStrategy); + + const startPromise = newPromise(); + + aos.InitializeTransformStream( + this, startPromise, writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm + ); + aos.SetUpTransformStreamDefaultControllerFromTransformer(this, transformer, transformerDict); + + if ('start' in transformerDict) { + resolvePromise(startPromise, transformerDict.start.call(transformer, this._transformStreamController)); + } else { + resolvePromise(startPromise, undefined); + } + } + + get readable() { + return this._readable; + } + + get writable() { + return this._writable; + } +}; diff --git a/reference-implementation/lib/TransformStream.webidl b/reference-implementation/lib/TransformStream.webidl new file mode 100644 index 000000000..3d02834c7 --- /dev/null +++ b/reference-implementation/lib/TransformStream.webidl @@ -0,0 +1,9 @@ +[Exposed=(Window,Worker,Worklet)] +interface TransformStream { + constructor(optional object transformer, + optional QueuingStrategy writableStrategy = {}, + optional QueuingStrategy readableStrategy = {}); + + readonly attribute ReadableStream readable; + readonly attribute WritableStream writable; +}; diff --git a/reference-implementation/lib/TransformStreamDefaultController-impl.js b/reference-implementation/lib/TransformStreamDefaultController-impl.js new file mode 100644 index 000000000..b3e87b7e8 --- /dev/null +++ b/reference-implementation/lib/TransformStreamDefaultController-impl.js @@ -0,0 +1,23 @@ +'use strict'; + +const aos = require('./abstract-ops/transform-streams.js'); +const rsAOs = require('./abstract-ops/readable-streams.js'); + +exports.implementation = class TransformStreamDefaultController { + get desiredSize() { + const readableController = this._controlledTransformStream._readable._readableStreamController; + return rsAOs.ReadableStreamDefaultControllerGetDesiredSize(readableController); + } + + enqueue(chunk) { + aos.TransformStreamDefaultControllerEnqueue(this, chunk); + } + + error(reason) { + aos.TransformStreamDefaultControllerError(this, reason); + } + + terminate() { + aos.TransformStreamDefaultControllerTerminate(this); + } +}; diff --git a/reference-implementation/lib/TransformStreamDefaultController.webidl b/reference-implementation/lib/TransformStreamDefaultController.webidl new file mode 100644 index 000000000..8602a4edf --- /dev/null +++ b/reference-implementation/lib/TransformStreamDefaultController.webidl @@ -0,0 +1,8 @@ +[Exposed=(Window,Worker,Worklet)] +interface TransformStreamDefaultController { + readonly attribute unrestricted double? desiredSize; + + void enqueue(optional any chunk); + void error(optional any reason); + void terminate(); +}; diff --git a/reference-implementation/lib/Transformer.webidl b/reference-implementation/lib/Transformer.webidl new file mode 100644 index 000000000..06fa3a84a --- /dev/null +++ b/reference-implementation/lib/Transformer.webidl @@ -0,0 +1,11 @@ +dictionary Transformer { + TransformerStartCallback start; + TransformerTransformCallback transform; + TransformerFlushCallback flush; + any readableType; + any writableType; +}; + +callback TransformerStartCallback = any (TransformStreamDefaultController controller); +callback TransformerFlushCallback = Promise (TransformStreamDefaultController controller); +callback TransformerTransformCallback = Promise (TransformStreamDefaultController controller, optional any chunk); diff --git a/reference-implementation/lib/UnderlyingSink.webidl b/reference-implementation/lib/UnderlyingSink.webidl new file mode 100644 index 000000000..b8287e8b0 --- /dev/null +++ b/reference-implementation/lib/UnderlyingSink.webidl @@ -0,0 +1,12 @@ +dictionary UnderlyingSink { + UnderlyingSinkStartCallback start; + UnderlyingSinkWriteCallback write; + UnderlyingSinkCloseCallback close; + UnderlyingSinkAbortCallback abort; + any type; +}; + +callback UnderlyingSinkStartCallback = any (WritableStreamDefaultController controller); +callback UnderlyingSinkWriteCallback = Promise (WritableStreamDefaultController controller, optional any chunk); +callback UnderlyingSinkCloseCallback = Promise (); +callback UnderlyingSinkAbortCallback = Promise (optional any reason); diff --git a/reference-implementation/lib/UnderlyingSource.webidl b/reference-implementation/lib/UnderlyingSource.webidl new file mode 100644 index 000000000..a048269b5 --- /dev/null +++ b/reference-implementation/lib/UnderlyingSource.webidl @@ -0,0 +1,15 @@ +dictionary UnderlyingSource { + UnderlyingSourceStartCallback start; + UnderlyingSourcePullCallback pull; + UnderlyingSourceCancelCallback cancel; + ReadableStreamType type; + [EnforceRange] unsigned long long autoAllocateChunkSize; +}; + +typedef (ReadableStreamDefaultController or ReadableByteStreamController) ReadableStreamController; + +callback UnderlyingSourceStartCallback = any (ReadableStreamController controller); +callback UnderlyingSourcePullCallback = Promise (ReadableStreamController controller); +callback UnderlyingSourceCancelCallback = Promise (optional any reason); + +enum ReadableStreamType { "bytes" }; diff --git a/reference-implementation/lib/WritableStream-impl.js b/reference-implementation/lib/WritableStream-impl.js new file mode 100644 index 000000000..7d23802d4 --- /dev/null +++ b/reference-implementation/lib/WritableStream-impl.js @@ -0,0 +1,56 @@ +'use strict'; + +const { promiseRejectedWith } = require('./helpers/webidl.js'); +const { ExtractHighWaterMark, ExtractSizeAlgorithm } = require('./abstract-ops/queuing-strategy.js'); +const aos = require('./abstract-ops/writable-streams.js'); + +const UnderlyingSink = require('../generated/UnderlyingSink.js'); + +exports.implementation = class WritableStreamImpl { + constructor(globalObject, [underlyingSink, strategy]) { + if (underlyingSink === undefined) { + underlyingSink = null; + } + const underlyingSinkDict = UnderlyingSink.convert(underlyingSink); + if ('type' in underlyingSinkDict) { + throw new RangeError('Invalid type is specified'); + } + + aos.InitializeWritableStream(this); + + const sizeAlgorithm = ExtractSizeAlgorithm(strategy); + const highWaterMark = ExtractHighWaterMark(strategy, 1); + + aos.SetUpWritableStreamDefaultControllerFromUnderlyingSink( + this, underlyingSink, underlyingSinkDict, highWaterMark, sizeAlgorithm + ); + } + + get locked() { + return aos.IsWritableStreamLocked(this); + } + + abort(reason) { + if (aos.IsWritableStreamLocked(this) === true) { + return promiseRejectedWith(new TypeError('Cannot abort a stream that already has a writer')); + } + + return aos.WritableStreamAbort(this, reason); + } + + close() { + if (aos.IsWritableStreamLocked(this) === true) { + return promiseRejectedWith(new TypeError('Cannot close a stream that already has a writer')); + } + + if (aos.WritableStreamCloseQueuedOrInFlight(this) === true) { + return promiseRejectedWith(new TypeError('Cannot close an already-closing stream')); + } + + return aos.WritableStreamClose(this); + } + + getWriter() { + return aos.AcquireWritableStreamDefaultWriter(this); + } +}; diff --git a/reference-implementation/lib/WritableStream.webidl b/reference-implementation/lib/WritableStream.webidl new file mode 100644 index 000000000..1cea8a8b0 --- /dev/null +++ b/reference-implementation/lib/WritableStream.webidl @@ -0,0 +1,10 @@ +[Exposed=(Window,Worker,Worklet)] +interface WritableStream { + constructor(optional object underlyingSink, optional QueuingStrategy strategy = {}); + + readonly attribute boolean locked; + + Promise abort(optional any reason); + Promise close(); + WritableStreamDefaultWriter getWriter(); +}; diff --git a/reference-implementation/lib/WritableStreamDefaultController-impl.js b/reference-implementation/lib/WritableStreamDefaultController-impl.js new file mode 100644 index 000000000..17a865ee2 --- /dev/null +++ b/reference-implementation/lib/WritableStreamDefaultController-impl.js @@ -0,0 +1,29 @@ +'use strict'; + +const aos = require('./abstract-ops/writable-streams.js'); +const { AbortSteps, ErrorSteps } = require('./abstract-ops/internal-methods.js'); +const { ResetQueue } = require('./abstract-ops/queue-with-sizes.js'); + +exports.implementation = class WritableStreamDefaultControllerImpl { + error(e) { + const state = this._controlledWritableStream._state; + + if (state !== 'writable') { + // The stream is closed, errored or will be soon. The sink can't do anything useful if it gets an error here, so + // just treat it as a no-op. + return; + } + + aos.WritableStreamDefaultControllerError(this, e); + } + + [AbortSteps](reason) { + const result = this._abortAlgorithm(reason); + aos.WritableStreamDefaultControllerClearAlgorithms(this); + return result; + } + + [ErrorSteps]() { + ResetQueue(this); + } +}; diff --git a/reference-implementation/lib/WritableStreamDefaultController.webidl b/reference-implementation/lib/WritableStreamDefaultController.webidl new file mode 100644 index 000000000..b127a7ab4 --- /dev/null +++ b/reference-implementation/lib/WritableStreamDefaultController.webidl @@ -0,0 +1,4 @@ +[Exposed=(Window,Worker,Worklet)] +interface WritableStreamDefaultController { + void error(optional any e); +}; diff --git a/reference-implementation/lib/WritableStreamDefaultWriter-impl.js b/reference-implementation/lib/WritableStreamDefaultWriter-impl.js new file mode 100644 index 000000000..689bae1bc --- /dev/null +++ b/reference-implementation/lib/WritableStreamDefaultWriter-impl.js @@ -0,0 +1,73 @@ +'use strict'; +const assert = require('assert'); + +const { promiseRejectedWith } = require('./helpers/webidl.js'); +const aos = require('./abstract-ops/writable-streams.js'); + +exports.implementation = class WritableStreamDefaultWriterImpl { + constructor(globalObject, [stream]) { + aos.SetUpWritableStreamDefaultWriter(this, stream); + } + + get closed() { + return this._closedPromise; + } + + get desiredSize() { + if (this._ownerWritableStream === undefined) { + throw defaultWriterLockException('desiredSize'); + } + + return aos.WritableStreamDefaultWriterGetDesiredSize(this); + } + + get ready() { + return this._readyPromise; + } + + abort(reason) { + if (this._ownerWritableStream === undefined) { + return promiseRejectedWith(defaultWriterLockException('abort')); + } + + return aos.WritableStreamDefaultWriterAbort(this, reason); + } + + close() { + const stream = this._ownerWritableStream; + + if (stream === undefined) { + return promiseRejectedWith(defaultWriterLockException('close')); + } + + if (aos.WritableStreamCloseQueuedOrInFlight(stream) === true) { + return promiseRejectedWith(new TypeError('Cannot close an already-closing stream')); + } + + return aos.WritableStreamDefaultWriterClose(this); + } + + releaseLock() { + const stream = this._ownerWritableStream; + + if (stream === undefined) { + return; + } + + assert(stream._writer !== undefined); + + aos.WritableStreamDefaultWriterRelease(this); + } + + write(chunk) { + if (this._ownerWritableStream === undefined) { + return promiseRejectedWith(defaultWriterLockException('write to')); + } + + return aos.WritableStreamDefaultWriterWrite(this, chunk); + } +}; + +function defaultWriterLockException(name) { + return new TypeError('Cannot ' + name + ' a stream using a released writer'); +} diff --git a/reference-implementation/lib/WritableStreamDefaultWriter.webidl b/reference-implementation/lib/WritableStreamDefaultWriter.webidl new file mode 100644 index 000000000..42e0e1661 --- /dev/null +++ b/reference-implementation/lib/WritableStreamDefaultWriter.webidl @@ -0,0 +1,13 @@ +[Exposed=(Window,Worker,Worklet)] +interface WritableStreamDefaultWriter { + constructor(WritableStream stream); + + readonly attribute Promise closed; + readonly attribute unrestricted double? desiredSize; + readonly attribute Promise ready; + + Promise abort(optional any reason); + Promise close(); + void releaseLock(); + Promise write(optional any chunk); +}; diff --git a/reference-implementation/lib/abstract-ops/ecmascript.js b/reference-implementation/lib/abstract-ops/ecmascript.js new file mode 100644 index 000000000..636e352da --- /dev/null +++ b/reference-implementation/lib/abstract-ops/ecmascript.js @@ -0,0 +1,36 @@ +'use strict'; +const assert = require('assert'); + +const isFakeDetached = Symbol('is "detached" for our purposes'); + +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. + return elements.slice(); +}; + +exports.CopyDataBlockBytes = (dest, destOffset, src, srcOffset, n) => { + new Uint8Array(dest).set(new Uint8Array(src, srcOffset, n), destOffset); +}; + +// Not implemented correctly +exports.TransferArrayBuffer = O => { + assert(!exports.IsDetachedBuffer(O)); + const transferredIshVersion = O.slice(); + + // This is specifically to fool tests that test "is transferred" by taking a non-zero-length + // ArrayBuffer and checking if its byteLength starts returning 0. + Object.defineProperty(O, 'byteLength', { + get() { + return 0; + } + }); + O[isFakeDetached] = true; + + return transferredIshVersion; +}; + +// Not implemented correctly +exports.IsDetachedBuffer = O => { + return isFakeDetached in O; +}; diff --git a/reference-implementation/lib/abstract-ops/internal-methods.js b/reference-implementation/lib/abstract-ops/internal-methods.js new file mode 100644 index 000000000..1debfa6a7 --- /dev/null +++ b/reference-implementation/lib/abstract-ops/internal-methods.js @@ -0,0 +1,6 @@ +'use strict'; + +exports.AbortSteps = Symbol('[[AbortSteps]]'); +exports.ErrorSteps = Symbol('[[ErrorSteps]]'); +exports.CancelSteps = Symbol('[[CancelSteps]]'); +exports.PullSteps = Symbol('[[PullSteps]]'); diff --git a/reference-implementation/lib/abstract-ops/miscellaneous.js b/reference-implementation/lib/abstract-ops/miscellaneous.js new file mode 100644 index 000000000..9f5ff5449 --- /dev/null +++ b/reference-implementation/lib/abstract-ops/miscellaneous.js @@ -0,0 +1,17 @@ +'use strict'; + +exports.IsNonNegativeNumber = v => { + if (typeof v !== 'number') { + return false; + } + + if (Number.isNaN(v)) { + return false; + } + + if (v < 0) { + return false; + } + + return true; +}; diff --git a/reference-implementation/lib/queue-with-sizes.js b/reference-implementation/lib/abstract-ops/queue-with-sizes.js similarity index 83% rename from reference-implementation/lib/queue-with-sizes.js rename to reference-implementation/lib/abstract-ops/queue-with-sizes.js index 328438fd8..22086caa5 100644 --- a/reference-implementation/lib/queue-with-sizes.js +++ b/reference-implementation/lib/abstract-ops/queue-with-sizes.js @@ -1,6 +1,6 @@ 'use strict'; const assert = require('assert'); -const { IsFiniteNonNegativeNumber } = require('./helpers.js'); +const { IsNonNegativeNumber } = require('./miscellaneous.js'); exports.DequeueValue = container => { assert('_queue' in container && '_queueTotalSize' in container); @@ -18,8 +18,10 @@ exports.DequeueValue = container => { exports.EnqueueValueWithSize = (container, value, size) => { assert('_queue' in container && '_queueTotalSize' in container); - size = Number(size); - if (!IsFiniteNonNegativeNumber(size)) { + if (!IsNonNegativeNumber(size)) { + throw new RangeError('Size must be a finite, non-NaN, non-negative number.'); + } + if (size === Infinity) { throw new RangeError('Size must be a finite, non-NaN, non-negative number.'); } diff --git a/reference-implementation/lib/abstract-ops/queuing-strategy.js b/reference-implementation/lib/abstract-ops/queuing-strategy.js new file mode 100644 index 000000000..7fdc0a8fa --- /dev/null +++ b/reference-implementation/lib/abstract-ops/queuing-strategy.js @@ -0,0 +1,25 @@ +'use strict'; + +exports.ExtractHighWaterMark = (strategy, defaultHWM) => { + if (!('highWaterMark' in strategy)) { + return defaultHWM; + } + + const { highWaterMark } = strategy; + if (Number.isNaN(highWaterMark) || highWaterMark < 0) { + throw new RangeError('Invalid highWaterMark'); + } + + return highWaterMark; +}; + +exports.ExtractSizeAlgorithm = strategy => { + const { size } = strategy; + + if (!size) { + return () => 1; + } + + // This is silly, but more obviously matches the spec (which distinguishes between algorithms and JS functions). + return chunk => size(chunk); +}; diff --git a/reference-implementation/lib/readable-stream.js b/reference-implementation/lib/abstract-ops/readable-streams.js similarity index 51% rename from reference-implementation/lib/readable-stream.js rename to reference-implementation/lib/abstract-ops/readable-streams.js index 371dd6166..9959abe97 100644 --- a/reference-implementation/lib/readable-stream.js +++ b/reference-implementation/lib/abstract-ops/readable-streams.js @@ -1,274 +1,90 @@ 'use strict'; -/* global AbortSignal:false */ - const assert = require('assert'); -const { ArrayBufferCopy, CreateAlgorithmFromUnderlyingMethod, IsFiniteNonNegativeNumber, InvokeOrNoop, - IsDetachedBuffer, TransferArrayBuffer, ValidateAndNormalizeHighWaterMark, IsNonNegativeNumber, - MakeSizeAlgorithmFromSizeFunction, createArrayFromList, typeIsObject, WaitForAllPromise, - newPromise, promiseResolvedWith, promiseRejectedWith, uponPromise, uponFulfillment, uponRejection, - transformPromiseWith, setPromiseIsHandledToTrue } = require('./helpers.js'); -const { DequeueValue, EnqueueValueWithSize, ResetQueue } = require('./queue-with-sizes.js'); -const { AcquireWritableStreamDefaultWriter, IsWritableStream, IsWritableStreamLocked, - WritableStreamAbort, WritableStreamDefaultWriterCloseWithErrorPropagation, - WritableStreamDefaultWriterRelease, WritableStreamDefaultWriterWrite, WritableStreamCloseQueuedOrInFlight } = - require('./writable-stream.js'); - -const CancelSteps = Symbol('[[CancelSteps]]'); -const PullSteps = Symbol('[[PullSteps]]'); - -class ReadableStream { - constructor(underlyingSource = {}, strategy = {}) { - InitializeReadableStream(this); - - const size = strategy.size; - let highWaterMark = strategy.highWaterMark; - - const type = underlyingSource.type; - const typeString = String(type); - if (typeString === 'bytes') { - if (size !== undefined) { - throw new RangeError('The strategy for a byte stream cannot have a size function'); - } - - if (highWaterMark === undefined) { - highWaterMark = 0; - } - highWaterMark = ValidateAndNormalizeHighWaterMark(highWaterMark); - - SetUpReadableByteStreamControllerFromUnderlyingSource(this, underlyingSource, highWaterMark); - } else if (type === undefined) { - const sizeAlgorithm = MakeSizeAlgorithmFromSizeFunction(size); - - if (highWaterMark === undefined) { - highWaterMark = 1; - } - highWaterMark = ValidateAndNormalizeHighWaterMark(highWaterMark); - - SetUpReadableStreamDefaultControllerFromUnderlyingSource(this, underlyingSource, highWaterMark, sizeAlgorithm); - } else { - throw new RangeError('Invalid type is specified'); - } - } - - get locked() { - if (IsReadableStream(this) === false) { - throw streamBrandCheckException('locked'); - } - - return IsReadableStreamLocked(this); - } - - cancel(reason) { - if (IsReadableStream(this) === false) { - return promiseRejectedWith(streamBrandCheckException('cancel')); - } - - if (IsReadableStreamLocked(this) === true) { - return promiseRejectedWith(new TypeError('Cannot cancel a stream that already has a reader')); - } - - return ReadableStreamCancel(this, reason); - } - - getReader({ mode } = {}) { - if (IsReadableStream(this) === false) { - throw streamBrandCheckException('getReader'); - } - - if (mode === undefined) { - return AcquireReadableStreamDefaultReader(this, true); - } - - mode = String(mode); - - if (mode === 'byob') { - return AcquireReadableStreamBYOBReader(this, true); - } - - throw new RangeError('Invalid mode is specified'); - } - - pipeThrough({ writable, readable }, { preventClose, preventAbort, preventCancel, signal } = {}) { - if (IsReadableStream(this) === false) { - throw streamBrandCheckException('pipeThrough'); - } - - if (IsWritableStream(writable) === false) { - throw new TypeError('writable argument to pipeThrough must be a WritableStream'); - } - - if (IsReadableStream(readable) === false) { - throw new TypeError('readable argument to pipeThrough must be a ReadableStream'); - } - - preventClose = Boolean(preventClose); - preventAbort = Boolean(preventAbort); - preventCancel = Boolean(preventCancel); - - if (signal !== undefined && !isAbortSignal(signal)) { - throw new TypeError('ReadableStream.prototype.pipeThrough\'s signal option must be an AbortSignal'); - } - - if (IsReadableStreamLocked(this) === true) { - throw new TypeError('ReadableStream.prototype.pipeThrough cannot be used on a locked ReadableStream'); - } - if (IsWritableStreamLocked(writable) === true) { - throw new TypeError('ReadableStream.prototype.pipeThrough cannot be used on a locked WritableStream'); - } - - const promise = ReadableStreamPipeTo(this, writable, preventClose, preventAbort, preventCancel, signal); - - setPromiseIsHandledToTrue(promise); - - return readable; - } - - pipeTo(dest, { preventClose, preventAbort, preventCancel, signal } = {}) { - if (IsReadableStream(this) === false) { - return promiseRejectedWith(streamBrandCheckException('pipeTo')); - } - if (IsWritableStream(dest) === false) { - return promiseRejectedWith( - new TypeError('ReadableStream.prototype.pipeTo\'s first argument must be a WritableStream')); - } - - preventClose = Boolean(preventClose); - preventAbort = Boolean(preventAbort); - preventCancel = Boolean(preventCancel); - - if (signal !== undefined && !isAbortSignal(signal)) { - return promiseRejectedWith( - new TypeError('ReadableStream.prototype.pipeTo\'s signal option must be an AbortSignal') - ); - } - - if (IsReadableStreamLocked(this) === true) { - return promiseRejectedWith( - new TypeError('ReadableStream.prototype.pipeTo cannot be used on a locked ReadableStream') - ); - } - if (IsWritableStreamLocked(dest) === true) { - return promiseRejectedWith( - new TypeError('ReadableStream.prototype.pipeTo cannot be used on a locked WritableStream') - ); - } - - return ReadableStreamPipeTo(this, dest, preventClose, preventAbort, preventCancel, signal); - } - - tee() { - if (IsReadableStream(this) === false) { - throw streamBrandCheckException('tee'); - } - - 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 promiseRejectedWith(streamAsyncIteratorBrandCheckException('next')); - } - const reader = this._asyncIteratorReader; - if (reader._ownerReadableStream === undefined) { - return promiseRejectedWith(readerLockException('iterate')); - } - return transformPromiseWith(ReadableStreamDefaultReaderRead(reader), result => { - assert(typeIsObject(result)); - const done = result.done; - assert(typeof done === 'boolean'); - if (done) { - ReadableStreamReaderGenericRelease(reader); - } - const value = result.value; - return ReadableStreamCreateReadResult(value, done, true); - }); - }, - - return(value) { - if (IsReadableStreamAsyncIterator(this) === false) { - return promiseRejectedWith(streamAsyncIteratorBrandCheckException('next')); - } - const reader = this._asyncIteratorReader; - if (reader._ownerReadableStream === undefined) { - return promiseRejectedWith(readerLockException('finish iterating')); - } - if (reader._readRequests.length > 0) { - return promiseRejectedWith(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 transformPromiseWith(result, () => ReadableStreamCreateReadResult(value, true, true)); - } - ReadableStreamReaderGenericRelease(reader); - return promiseResolvedWith(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 = { +const { promiseResolvedWith, promiseRejectedWith, newPromise, resolvePromise, rejectPromise, uponPromise, + setPromiseIsHandledToTrue, waitForAllPromise, transformPromiseWith, uponFulfillment, uponRejection } = + require('../helpers/webidl.js'); +const { typeIsObject } = require('../helpers/miscellaneous.js'); +const { CopyDataBlockBytes, CreateArrayFromList, TransferArrayBuffer } = require('./ecmascript.js'); +const { IsNonNegativeNumber } = require('./miscellaneous.js'); +const { EnqueueValueWithSize, ResetQueue } = require('./queue-with-sizes.js'); +const { AcquireWritableStreamDefaultWriter, IsWritableStreamLocked, WritableStreamAbort, + WritableStreamDefaultWriterCloseWithErrorPropagation, WritableStreamDefaultWriterRelease, + WritableStreamDefaultWriterWrite, WritableStreamCloseQueuedOrInFlight } = require('./writable-streams.js'); +const { CancelSteps, PullSteps } = require('./internal-methods.js'); + +const ReadableByteStreamController = require('../../generated/ReadableByteStreamController.js'); +const ReadableStreamBYOBReader = require('../../generated/ReadableStreamBYOBReader.js'); +const ReadableStreamDefaultReader = require('../../generated/ReadableStreamDefaultReader.js'); +const ReadableStreamDefaultController = require('../../generated/ReadableStreamDefaultController.js'); +const ReadableStream = require('../../generated/ReadableStream.js'); +const WritableStream = require('../../generated/WritableStream.js'); + +Object.assign(exports, { AcquireReadableStreamBYOBReader, AcquireReadableStreamDefaultReader, - CreateReadableByteStream, CreateReadableStream, - ReadableStream, - IsReadableStreamDisturbed, + InitializeReadableStream, + IsReadableStreamLocked, + ReadableByteStreamControllerCallPullIfNeeded, + ReadableByteStreamControllerClearAlgorithms, + ReadableByteStreamControllerClose, + ReadableByteStreamControllerEnqueue, + ReadableByteStreamControllerError, + ReadableByteStreamControllerGetDesiredSize, + ReadableByteStreamControllerHandleQueueDrain, + ReadableByteStreamControllerRespond, + ReadableByteStreamControllerRespondWithNewView, + ReadableStreamAddReadRequest, + ReadableStreamBYOBReaderRead, + ReadableStreamCancel, + ReadableStreamClose, + ReadableStreamCreateReadResult, + ReadableStreamDefaultControllerCallPullIfNeeded, + ReadableStreamDefaultControllerCanCloseOrEnqueue, + ReadableStreamDefaultControllerClearAlgorithms, ReadableStreamDefaultControllerClose, ReadableStreamDefaultControllerEnqueue, ReadableStreamDefaultControllerError, ReadableStreamDefaultControllerGetDesiredSize, ReadableStreamDefaultControllerHasBackpressure, - ReadableStreamDefaultControllerCanCloseOrEnqueue -}; + ReadableStreamDefaultReaderRead, + ReadableStreamGetNumReadRequests, + ReadableStreamHasDefaultReader, + ReadableStreamPipeTo, + ReadableStreamReaderGenericCancel, + ReadableStreamReaderGenericRelease, + ReadableStreamTee, + SetUpReadableByteStreamControllerFromUnderlyingSource, + SetUpReadableStreamBYOBReader, + SetUpReadableStreamDefaultControllerFromUnderlyingSource, + SetUpReadableStreamDefaultReader +}); -// Abstract operations for the ReadableStream. +// Working with readable streams function AcquireReadableStreamBYOBReader(stream, forAuthorCode = false) { - const reader = new ReadableStreamBYOBReader(stream); + const reader = ReadableStreamBYOBReader.new(globalThis); + SetUpReadableStreamBYOBReader(reader, stream); reader._forAuthorCode = forAuthorCode; return reader; } function AcquireReadableStreamDefaultReader(stream, forAuthorCode = false) { - const reader = new ReadableStreamDefaultReader(stream); + const reader = ReadableStreamDefaultReader.new(globalThis); + SetUpReadableStreamDefaultReader(reader, stream); reader._forAuthorCode = forAuthorCode; return reader; } -// Throws if and only if startAlgorithm throws. function CreateReadableStream(startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark = 1, sizeAlgorithm = () => 1) { assert(IsNonNegativeNumber(highWaterMark) === true); - const stream = Object.create(ReadableStream.prototype); + const stream = ReadableStream.new(globalThis); InitializeReadableStream(stream); - const controller = Object.create(ReadableStreamDefaultController.prototype); - + const controller = ReadableStreamDefaultController.new(globalThis); SetUpReadableStreamDefaultController( stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark, sizeAlgorithm ); @@ -276,25 +92,7 @@ function CreateReadableStream(startAlgorithm, pullAlgorithm, cancelAlgorithm, hi return stream; } -// Throws if and only if startAlgorithm throws. -function CreateReadableByteStream(startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark = 0, - autoAllocateChunkSize = undefined) { - assert(IsNonNegativeNumber(highWaterMark) === true); - if (autoAllocateChunkSize !== undefined) { - assert(Number.isInteger(autoAllocateChunkSize) === true); - assert(autoAllocateChunkSize > 0); - } - - const stream = Object.create(ReadableStream.prototype); - InitializeReadableStream(stream); - - const controller = Object.create(ReadableByteStreamController.prototype); - - SetUpReadableByteStreamController(stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark, - autoAllocateChunkSize); - - return stream; -} +// CreateReadableByteStream is not implemented since it is only meant for external specs. function InitializeReadableStream(stream) { stream._state = 'readable'; @@ -303,27 +101,9 @@ function InitializeReadableStream(stream) { stream._disturbed = false; } -function IsReadableStream(x) { - if (!typeIsObject(x)) { - return false; - } - - if (!Object.prototype.hasOwnProperty.call(x, '_readableStreamController')) { - return false; - } - - return true; -} - -function IsReadableStreamDisturbed(stream) { - assert(IsReadableStream(stream) === true); - - return stream._disturbed; -} +// IsReadableStreamDisturbed is not implemented since it is only meant for external specs. function IsReadableStreamLocked(stream) { - assert(IsReadableStream(stream) === true); - if (stream._reader === undefined) { return false; } @@ -331,25 +111,13 @@ 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); + assert(ReadableStream.isImpl(source)); + assert(WritableStream.isImpl(dest)); assert(typeof preventClose === 'boolean'); assert(typeof preventAbort === 'boolean'); assert(typeof preventCancel === 'boolean'); - assert(signal === undefined || isAbortSignal(signal)); + assert(signal === undefined || signal.constructor.name === 'AbortSignal'); assert(IsReadableStreamLocked(source) === false); assert(IsWritableStreamLocked(dest) === false); @@ -363,7 +131,7 @@ function ReadableStreamPipeTo(source, dest, preventClose, preventAbort, preventC // This is used to keep track of the spec's requirement that we wait for ongoing writes during shutdown. let currentWrite = promiseResolvedWith(undefined); - return newPromise((resolve, reject) => { + return new Promise((resolve, reject) => { let abortAlgorithm; if (signal !== undefined) { abortAlgorithm = () => { @@ -385,7 +153,7 @@ function ReadableStreamPipeTo(source, dest, preventClose, preventAbort, preventC return promiseResolvedWith(undefined); }); } - shutdownWithAction(() => WaitForAllPromise(actions.map(action => action()), results => results), true, error); + shutdownWithAction(() => waitForAllPromise(actions.map(action => action()), results => results), true, error); }; if (signal.aborted === true) { @@ -400,7 +168,7 @@ function ReadableStreamPipeTo(source, dest, preventClose, preventAbort, preventC // - Backpressure must be enforced // - Shutdown must stop all activity function pipeLoop() { - return newPromise((resolveLoop, rejectLoop) => { + return new Promise((resolveLoop, rejectLoop) => { function next(done) { if (done) { resolveLoop(); @@ -547,7 +315,7 @@ function ReadableStreamPipeTo(source, dest, preventClose, preventAbort, preventC } function ReadableStreamTee(stream, cloneForBranch2) { - assert(IsReadableStream(stream) === true); + assert(ReadableStream.isImpl(stream)); assert(typeof cloneForBranch2 === 'boolean'); const reader = AcquireReadableStreamDefaultReader(stream); @@ -561,7 +329,7 @@ function ReadableStreamTee(stream, cloneForBranch2) { let branch2; let resolveCancelPromise; - const cancelPromise = newPromise(resolve => { + const cancelPromise = new Promise(resolve => { resolveCancelPromise = resolve; }); @@ -617,7 +385,7 @@ function ReadableStreamTee(stream, cloneForBranch2) { canceled1 = true; reason1 = reason; if (canceled2 === true) { - const compositeReason = createArrayFromList([reason1, reason2]); + const compositeReason = CreateArrayFromList([reason1, reason2]); const cancelResult = ReadableStreamCancel(stream, compositeReason); resolveCancelPromise(cancelResult); } @@ -628,7 +396,7 @@ function ReadableStreamTee(stream, cloneForBranch2) { canceled2 = true; reason2 = reason; if (canceled1 === true) { - const compositeReason = createArrayFromList([reason1, reason2]); + const compositeReason = CreateArrayFromList([reason1, reason2]); const cancelResult = ReadableStreamCancel(stream, compositeReason); resolveCancelPromise(cancelResult); } @@ -648,37 +416,23 @@ function ReadableStreamTee(stream, cloneForBranch2) { return [branch1, branch2]; } -// ReadableStream API exposed for controllers. +// Interfacing with controllers function ReadableStreamAddReadIntoRequest(stream) { - assert(IsReadableStreamBYOBReader(stream._reader) === true); + assert(ReadableStreamBYOBReader.isImpl(stream._reader)); assert(stream._state === 'readable' || stream._state === 'closed'); - const promise = newPromise((resolve, reject) => { - const readIntoRequest = { - _resolve: resolve, - _reject: reject - }; - - stream._reader._readIntoRequests.push(readIntoRequest); - }); - + const promise = newPromise(); + stream._reader._readIntoRequests.push(promise); return promise; } function ReadableStreamAddReadRequest(stream) { - assert(IsReadableStreamDefaultReader(stream._reader) === true); + assert(ReadableStreamDefaultReader.isImpl(stream._reader)); assert(stream._state === 'readable'); - const promise = newPromise((resolve, reject) => { - const readRequest = { - _resolve: resolve, - _reject: reject - }; - - stream._reader._readRequests.push(readRequest); - }); - + const promise = newPromise(); + stream._reader._readRequests.push(promise); return promise; } @@ -709,14 +463,14 @@ function ReadableStreamClose(stream) { return; } - if (IsReadableStreamDefaultReader(reader) === true) { - for (const { _resolve } of reader._readRequests) { - _resolve(ReadableStreamCreateReadResult(undefined, true, reader._forAuthorCode)); + if (ReadableStreamDefaultReader.isImpl(reader)) { + for (const readRequest of reader._readRequests) { + resolvePromise(readRequest, ReadableStreamCreateReadResult(undefined, true, reader._forAuthorCode)); } reader._readRequests = []; } - defaultReaderClosedPromiseResolve(reader); + resolvePromise(reader._closedPromise, undefined); } function ReadableStreamCreateReadResult(value, done, forAuthorCode) { @@ -732,7 +486,6 @@ function ReadableStreamCreateReadResult(value, done, forAuthorCode) { } function ReadableStreamError(stream, e) { - assert(IsReadableStream(stream) === true); assert(stream._state === 'readable'); stream._state = 'errored'; @@ -744,23 +497,23 @@ function ReadableStreamError(stream, e) { return; } - if (IsReadableStreamDefaultReader(reader) === true) { + if (ReadableStreamDefaultReader.isImpl(reader)) { for (const readRequest of reader._readRequests) { - readRequest._reject(e); + rejectPromise(readRequest, e); } reader._readRequests = []; } else { - assert(IsReadableStreamBYOBReader(reader)); + assert(ReadableStreamBYOBReader.isImpl(reader)); for (const readIntoRequest of reader._readIntoRequests) { - readIntoRequest._reject(e); + rejectPromise(readIntoRequest, e); } reader._readIntoRequests = []; } - defaultReaderClosedPromiseReject(reader, e); + rejectPromise(reader._closedPromise, e); setPromiseIsHandledToTrue(reader._closedPromise); } @@ -770,7 +523,7 @@ function ReadableStreamFulfillReadIntoRequest(stream, chunk, done) { assert(reader._readIntoRequests.length > 0); const readIntoRequest = reader._readIntoRequests.shift(); - readIntoRequest._resolve(ReadableStreamCreateReadResult(chunk, done, reader._forAuthorCode)); + resolvePromise(readIntoRequest, ReadableStreamCreateReadResult(chunk, done, reader._forAuthorCode)); } function ReadableStreamFulfillReadRequest(stream, chunk, done) { @@ -779,7 +532,7 @@ function ReadableStreamFulfillReadRequest(stream, chunk, done) { assert(reader._readRequests.length > 0); const readRequest = reader._readRequests.shift(); - readRequest._resolve(ReadableStreamCreateReadResult(chunk, done, reader._forAuthorCode)); + resolvePromise(readRequest, ReadableStreamCreateReadResult(chunk, done, reader._forAuthorCode)); } function ReadableStreamGetNumReadIntoRequests(stream) { @@ -797,11 +550,11 @@ function ReadableStreamHasBYOBReader(stream) { return false; } - if (IsReadableStreamBYOBReader(reader) === false) { - return false; + if (ReadableStreamBYOBReader.isImpl(reader)) { + return true; } - return true; + return false; } function ReadableStreamHasDefaultReader(stream) { @@ -811,182 +564,19 @@ function ReadableStreamHasDefaultReader(stream) { return false; } - if (IsReadableStreamDefaultReader(reader) === false) { - return false; + if (ReadableStreamDefaultReader.isImpl(reader)) { + return true; } - return true; + return false; } // Readers -class ReadableStreamDefaultReader { - constructor(stream) { - if (IsReadableStream(stream) === false) { - throw new TypeError('ReadableStreamDefaultReader can only be constructed with a ReadableStream instance'); - } - if (IsReadableStreamLocked(stream) === true) { - throw new TypeError('This stream has already been locked for exclusive reading by another reader'); - } - - ReadableStreamReaderGenericInitialize(this, stream); - - this._readRequests = []; - } - - get closed() { - if (IsReadableStreamDefaultReader(this) === false) { - return promiseRejectedWith(defaultReaderBrandCheckException('closed')); - } - - return this._closedPromise; - } - - cancel(reason) { - if (IsReadableStreamDefaultReader(this) === false) { - return promiseRejectedWith(defaultReaderBrandCheckException('cancel')); - } - - if (this._ownerReadableStream === undefined) { - return promiseRejectedWith(readerLockException('cancel')); - } - - return ReadableStreamReaderGenericCancel(this, reason); - } - - read() { - if (IsReadableStreamDefaultReader(this) === false) { - return promiseRejectedWith(defaultReaderBrandCheckException('read')); - } - - if (this._ownerReadableStream === undefined) { - return promiseRejectedWith(readerLockException('read from')); - } - - return ReadableStreamDefaultReaderRead(this); - } - - releaseLock() { - if (IsReadableStreamDefaultReader(this) === false) { - throw defaultReaderBrandCheckException('releaseLock'); - } - - if (this._ownerReadableStream === undefined) { - return; - } - - if (this._readRequests.length > 0) { - throw new TypeError('Tried to release a reader lock when that reader has pending read() calls un-settled'); - } - - ReadableStreamReaderGenericRelease(this); - } -} - -class ReadableStreamBYOBReader { - constructor(stream) { - if (!IsReadableStream(stream)) { - throw new TypeError('ReadableStreamBYOBReader can only be constructed with a ReadableStream instance given a ' + - 'byte source'); - } - if (IsReadableByteStreamController(stream._readableStreamController) === false) { - throw new TypeError('Cannot construct a ReadableStreamBYOBReader for a stream not constructed with a byte ' + - 'source'); - } - if (IsReadableStreamLocked(stream)) { - throw new TypeError('This stream has already been locked for exclusive reading by another reader'); - } - - ReadableStreamReaderGenericInitialize(this, stream); - - this._readIntoRequests = []; - } - - get closed() { - if (!IsReadableStreamBYOBReader(this)) { - return promiseRejectedWith(byobReaderBrandCheckException('closed')); - } - - return this._closedPromise; - } - - cancel(reason) { - if (!IsReadableStreamBYOBReader(this)) { - return promiseRejectedWith(byobReaderBrandCheckException('cancel')); - } - - if (this._ownerReadableStream === undefined) { - return promiseRejectedWith(readerLockException('cancel')); - } - - return ReadableStreamReaderGenericCancel(this, reason); - } - - read(view) { - if (!IsReadableStreamBYOBReader(this)) { - return promiseRejectedWith(byobReaderBrandCheckException('read')); - } - - if (this._ownerReadableStream === undefined) { - return promiseRejectedWith(readerLockException('read from')); - } - - if (!ArrayBuffer.isView(view)) { - return promiseRejectedWith(new TypeError('view must be an array buffer view')); - } - - if (IsDetachedBuffer(view.buffer) === true) { - return promiseRejectedWith(new TypeError('Cannot read into a view onto a detached ArrayBuffer')); - } - - if (view.byteLength === 0) { - return promiseRejectedWith(new TypeError('view must have non-zero byteLength')); - } - - return ReadableStreamBYOBReaderRead(this, view); - } - - releaseLock() { - if (!IsReadableStreamBYOBReader(this)) { - throw byobReaderBrandCheckException('releaseLock'); - } - - if (this._ownerReadableStream === undefined) { - return; - } - - if (this._readIntoRequests.length > 0) { - throw new TypeError('Tried to release a reader lock when that reader has pending read() calls un-settled'); - } - - ReadableStreamReaderGenericRelease(this); - } -} - -// Abstract operations for the readers. - -function IsReadableStreamBYOBReader(x) { - if (!typeIsObject(x)) { - return false; - } - - if (!Object.prototype.hasOwnProperty.call(x, '_readIntoRequests')) { - return false; - } - - return true; -} - -function IsReadableStreamDefaultReader(x) { - if (!typeIsObject(x)) { - return false; - } - - if (!Object.prototype.hasOwnProperty.call(x, '_readRequests')) { - return false; - } - - return true; +function ReadableStreamReaderGenericCancel(reader, reason) { + const stream = reader._ownerReadableStream; + assert(stream !== undefined); + return ReadableStreamCancel(stream, reason); } function ReadableStreamReaderGenericInitialize(reader, stream) { @@ -995,38 +585,30 @@ function ReadableStreamReaderGenericInitialize(reader, stream) { stream._reader = reader; if (stream._state === 'readable') { - defaultReaderClosedPromiseInitialize(reader); + reader._closedPromise = newPromise(); } else if (stream._state === 'closed') { - defaultReaderClosedPromiseInitializeAsResolved(reader); + reader._closedPromise = promiseResolvedWith(undefined); } else { assert(stream._state === 'errored'); - defaultReaderClosedPromiseInitializeAsRejected(reader, stream._storedError); + reader._closedPromise = promiseRejectedWith(stream._storedError); setPromiseIsHandledToTrue(reader._closedPromise); } } -// A client of ReadableStreamDefaultReader and ReadableStreamBYOBReader may use these functions directly to bypass state -// check. - -function ReadableStreamReaderGenericCancel(reader, reason) { - const stream = reader._ownerReadableStream; - assert(stream !== undefined); - return ReadableStreamCancel(stream, reason); -} - function ReadableStreamReaderGenericRelease(reader) { assert(reader._ownerReadableStream !== undefined); assert(reader._ownerReadableStream._reader === 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')); + rejectPromise( + reader._closedPromise, + 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._closedPromise = promiseRejectedWith( + new TypeError('Reader was released and can no longer be used to monitor the stream\'s closedness') + ); } setPromiseIsHandledToTrue(reader._closedPromise); @@ -1045,7 +627,6 @@ function ReadableStreamBYOBReaderRead(reader, view) { return promiseRejectedWith(stream._storedError); } - // Controllers must implement this. return ReadableByteStreamControllerPullInto(stream._readableStreamController, view); } @@ -1069,110 +650,46 @@ function ReadableStreamDefaultReaderRead(reader) { return stream._readableStreamController[PullSteps](); } -// Controllers - -class ReadableStreamDefaultController { - constructor() { - throw new TypeError(); +function SetUpReadableStreamBYOBReader(reader, stream) { + if (IsReadableStreamLocked(stream) === true) { + throw new TypeError('This stream has already been locked for exclusive reading by another reader'); } - get desiredSize() { - if (IsReadableStreamDefaultController(this) === false) { - throw defaultControllerBrandCheckException('desiredSize'); - } - - return ReadableStreamDefaultControllerGetDesiredSize(this); + if (!ReadableByteStreamController.isImpl(stream._readableStreamController)) { + throw new TypeError('Cannot construct a ReadableStreamBYOBReader for a stream not constructed with a byte source'); } - close() { - if (IsReadableStreamDefaultController(this) === false) { - throw defaultControllerBrandCheckException('close'); - } + ReadableStreamReaderGenericInitialize(reader, stream); - if (ReadableStreamDefaultControllerCanCloseOrEnqueue(this) === false) { - throw new TypeError('The stream is not in a state that permits close'); - } + reader._readIntoRequests = []; +} - ReadableStreamDefaultControllerClose(this); +function SetUpReadableStreamDefaultReader(reader, stream) { + if (IsReadableStreamLocked(stream) === true) { + throw new TypeError('This stream has already been locked for exclusive reading by another reader'); } - enqueue(chunk) { - if (IsReadableStreamDefaultController(this) === false) { - throw defaultControllerBrandCheckException('enqueue'); - } - - if (ReadableStreamDefaultControllerCanCloseOrEnqueue(this) === false) { - throw new TypeError('The stream is not in a state that permits enqueue'); - } + ReadableStreamReaderGenericInitialize(reader, stream); - return ReadableStreamDefaultControllerEnqueue(this, chunk); - } + reader._readRequests = []; +} - error(e) { - if (IsReadableStreamDefaultController(this) === false) { - throw defaultControllerBrandCheckException('error'); - } +// Default controllers - ReadableStreamDefaultControllerError(this, e); +function ReadableStreamDefaultControllerCallPullIfNeeded(controller) { + const shouldPull = ReadableStreamDefaultControllerShouldCallPull(controller); + if (shouldPull === false) { + return; } - [CancelSteps](reason) { - ResetQueue(this); - const result = this._cancelAlgorithm(reason); - ReadableStreamDefaultControllerClearAlgorithms(this); - return result; + if (controller._pulling === true) { + controller._pullAgain = true; + return; } - [PullSteps]() { - const stream = this._controlledReadableStream; + assert(controller._pullAgain === false); - if (this._queue.length > 0) { - const chunk = DequeueValue(this); - - if (this._closeRequested === true && this._queue.length === 0) { - ReadableStreamDefaultControllerClearAlgorithms(this); - ReadableStreamClose(stream); - } else { - ReadableStreamDefaultControllerCallPullIfNeeded(this); - } - - return promiseResolvedWith(ReadableStreamCreateReadResult(chunk, false, stream._reader._forAuthorCode)); - } - - const pendingPromise = ReadableStreamAddReadRequest(stream); - ReadableStreamDefaultControllerCallPullIfNeeded(this); - return pendingPromise; - } -} - -// Abstract operations for the ReadableStreamDefaultController. - -function IsReadableStreamDefaultController(x) { - if (!typeIsObject(x)) { - return false; - } - - if (!Object.prototype.hasOwnProperty.call(x, '_controlledReadableStream')) { - return false; - } - - return true; -} - -function ReadableStreamDefaultControllerCallPullIfNeeded(controller) { - const shouldPull = ReadableStreamDefaultControllerShouldCallPull(controller); - if (shouldPull === false) { - return; - } - - if (controller._pulling === true) { - controller._pullAgain = true; - return; - } - - assert(controller._pullAgain === false); - - controller._pulling = true; + controller._pulling = true; const pullPromise = controller._pullAlgorithm(); uponPromise( @@ -1292,7 +809,6 @@ function ReadableStreamDefaultControllerGetDesiredSize(controller) { return controller._strategyHWM - controller._queueTotalSize; } -// This is used in the implementation of TransformStream. function ReadableStreamDefaultControllerHasBackpressure(controller) { if (ReadableStreamDefaultControllerShouldCallPull(controller) === true) { return false; @@ -1317,6 +833,7 @@ function SetUpReadableStreamDefaultController( controller._controlledReadableStream = stream; + // Need to set the slots so that the assert doesn't fire. In the spec the slots already exist implicitly. controller._queue = undefined; controller._queueTotalSize = undefined; ResetQueue(controller); @@ -1351,245 +868,32 @@ function SetUpReadableStreamDefaultController( ); } -function SetUpReadableStreamDefaultControllerFromUnderlyingSource(stream, underlyingSource, highWaterMark, - sizeAlgorithm) { +function SetUpReadableStreamDefaultControllerFromUnderlyingSource( + stream, underlyingSource, underlyingSourceDict, highWaterMark, sizeAlgorithm) { assert(underlyingSource !== undefined); - const controller = Object.create(ReadableStreamDefaultController.prototype); - - function startAlgorithm() { - return InvokeOrNoop(underlyingSource, 'start', [controller]); - } - - const pullAlgorithm = CreateAlgorithmFromUnderlyingMethod(underlyingSource, 'pull', 0, [controller]); - const cancelAlgorithm = CreateAlgorithmFromUnderlyingMethod(underlyingSource, 'cancel', 1, []); + const controller = ReadableStreamDefaultController.new(globalThis); - SetUpReadableStreamDefaultController(stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, - highWaterMark, sizeAlgorithm); -} + let startAlgorithm = () => undefined; + let pullAlgorithm = () => promiseResolvedWith(undefined); + let cancelAlgorithm = () => promiseResolvedWith(undefined); -class ReadableStreamBYOBRequest { - constructor() { - throw new TypeError('ReadableStreamBYOBRequest cannot be used directly'); + if ('start' in underlyingSourceDict) { + startAlgorithm = () => underlyingSourceDict.start.call(underlyingSource, controller); } - - get view() { - if (IsReadableStreamBYOBRequest(this) === false) { - throw byobRequestBrandCheckException('view'); - } - - return this._view; + if ('pull' in underlyingSourceDict) { + pullAlgorithm = () => underlyingSourceDict.pull.call(underlyingSource, controller); } - - respond(bytesWritten) { - if (IsReadableStreamBYOBRequest(this) === false) { - throw byobRequestBrandCheckException('respond'); - } - - if (this._associatedReadableByteStreamController === undefined) { - throw new TypeError('This BYOB request has been invalidated'); - } - - if (IsDetachedBuffer(this._view.buffer) === true) { - throw new TypeError('The BYOB request\'s buffer has been detached and so cannot be used as a response'); - } - - ReadableByteStreamControllerRespond(this._associatedReadableByteStreamController, bytesWritten); + if ('cancel' in underlyingSourceDict) { + cancelAlgorithm = reason => underlyingSourceDict.cancel.call(underlyingSource, reason); } - respondWithNewView(view) { - if (IsReadableStreamBYOBRequest(this) === false) { - throw byobRequestBrandCheckException('respond'); - } - - if (this._associatedReadableByteStreamController === undefined) { - throw new TypeError('This BYOB request has been invalidated'); - } - - if (!ArrayBuffer.isView(view)) { - throw new TypeError('You can only respond with array buffer views'); - } - - if (IsDetachedBuffer(view.buffer) === true) { - throw new TypeError('The supplied view\'s buffer has been detached and so cannot be used as a response'); - } - - ReadableByteStreamControllerRespondWithNewView(this._associatedReadableByteStreamController, view); - } -} - -class ReadableByteStreamController { - constructor() { - throw new TypeError('ReadableByteStreamController constructor cannot be used directly'); - } - - get byobRequest() { - if (IsReadableByteStreamController(this) === false) { - throw byteStreamControllerBrandCheckException('byobRequest'); - } - - if (this._byobRequest === undefined && this._pendingPullIntos.length > 0) { - const firstDescriptor = this._pendingPullIntos[0]; - const view = new Uint8Array(firstDescriptor.buffer, - firstDescriptor.byteOffset + firstDescriptor.bytesFilled, - firstDescriptor.byteLength - firstDescriptor.bytesFilled); - - const byobRequest = Object.create(ReadableStreamBYOBRequest.prototype); - SetUpReadableStreamBYOBRequest(byobRequest, this, view); - this._byobRequest = byobRequest; - } - - return this._byobRequest; - } - - get desiredSize() { - if (IsReadableByteStreamController(this) === false) { - throw byteStreamControllerBrandCheckException('desiredSize'); - } - - return ReadableByteStreamControllerGetDesiredSize(this); - } - - close() { - if (IsReadableByteStreamController(this) === false) { - throw byteStreamControllerBrandCheckException('close'); - } - - if (this._closeRequested === true) { - throw new TypeError('The stream has already been closed; do not close it again!'); - } - - const state = this._controlledReadableByteStream._state; - if (state !== 'readable') { - throw new TypeError(`The stream (in ${state} state) is not in the readable state and cannot be closed`); - } - - ReadableByteStreamControllerClose(this); - } - - enqueue(chunk) { - if (IsReadableByteStreamController(this) === false) { - throw byteStreamControllerBrandCheckException('enqueue'); - } - - if (this._closeRequested === true) { - throw new TypeError('stream is closed or draining'); - } - - const state = this._controlledReadableByteStream._state; - if (state !== 'readable') { - throw new TypeError(`The stream (in ${state} state) is not in the readable state and cannot be enqueued to`); - } - - if (!ArrayBuffer.isView(chunk)) { - throw new TypeError('You can only enqueue array buffer views when using a ReadableByteStreamController'); - } - - if (IsDetachedBuffer(chunk.buffer) === true) { - throw new TypeError('Cannot enqueue a view onto a detached ArrayBuffer'); - } - - ReadableByteStreamControllerEnqueue(this, chunk); - } - - error(e) { - if (IsReadableByteStreamController(this) === false) { - throw byteStreamControllerBrandCheckException('error'); - } - - ReadableByteStreamControllerError(this, e); - } - - [CancelSteps](reason) { - if (this._pendingPullIntos.length > 0) { - const firstDescriptor = this._pendingPullIntos[0]; - firstDescriptor.bytesFilled = 0; - } - - ResetQueue(this); - - const result = this._cancelAlgorithm(reason); - ReadableByteStreamControllerClearAlgorithms(this); - return result; - } - - [PullSteps]() { - const stream = this._controlledReadableByteStream; - assert(ReadableStreamHasDefaultReader(stream) === true); - - if (this._queueTotalSize > 0) { - assert(ReadableStreamGetNumReadRequests(stream) === 0); - - const entry = this._queue.shift(); - this._queueTotalSize -= entry.byteLength; - - ReadableByteStreamControllerHandleQueueDrain(this); - - let view; - try { - view = new Uint8Array(entry.buffer, entry.byteOffset, entry.byteLength); - } catch (viewE) { - return promiseRejectedWith(viewE); - } - - return promiseResolvedWith(ReadableStreamCreateReadResult(view, false, stream._reader._forAuthorCode)); - } - - const autoAllocateChunkSize = this._autoAllocateChunkSize; - if (autoAllocateChunkSize !== undefined) { - let buffer; - try { - buffer = new ArrayBuffer(autoAllocateChunkSize); - } catch (bufferE) { - return promiseRejectedWith(bufferE); - } - - const pullIntoDescriptor = { - buffer, - byteOffset: 0, - byteLength: autoAllocateChunkSize, - bytesFilled: 0, - elementSize: 1, - ctor: Uint8Array, - readerType: 'default' - }; - - this._pendingPullIntos.push(pullIntoDescriptor); - } - - const promise = ReadableStreamAddReadRequest(stream); - - ReadableByteStreamControllerCallPullIfNeeded(this); - - return promise; - } -} - -// Abstract operations for the ReadableByteStreamController. - -function IsReadableByteStreamController(x) { - if (!typeIsObject(x)) { - return false; - } - - if (!Object.prototype.hasOwnProperty.call(x, '_controlledReadableByteStream')) { - return false; - } - - return true; + SetUpReadableStreamDefaultController( + stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark, sizeAlgorithm + ); } -function IsReadableStreamBYOBRequest(x) { - if (!typeIsObject(x)) { - return false; - } - - if (!Object.prototype.hasOwnProperty.call(x, '_associatedReadableByteStreamController')) { - return false; - } - - return true; -} +// Byte stream controllers function ReadableByteStreamControllerCallPullIfNeeded(controller) { const shouldPull = ReadableByteStreamControllerShouldCallPull(controller); @@ -1624,11 +928,43 @@ function ReadableByteStreamControllerCallPullIfNeeded(controller) { ); } +function ReadableByteStreamControllerClearAlgorithms(controller) { + controller._pullAlgorithm = undefined; + controller._cancelAlgorithm = undefined; +} + function ReadableByteStreamControllerClearPendingPullIntos(controller) { ReadableByteStreamControllerInvalidateBYOBRequest(controller); controller._pendingPullIntos = []; } +function ReadableByteStreamControllerClose(controller) { + const stream = controller._controlledReadableStream; + + if (controller._closeRequest === true || stream._state !== 'readable') { + return; + } + + if (controller._queueTotalSize > 0) { + controller._closeRequested = true; + + return; + } + + if (controller._pendingPullIntos.length > 0) { + const firstPendingPullInto = controller._pendingPullIntos[0]; + if (firstPendingPullInto.bytesFilled > 0) { + const e = new TypeError('Insufficient bytes to fill elements in the given buffer'); + ReadableByteStreamControllerError(controller, e); + + throw e; + } + } + + ReadableByteStreamControllerClearAlgorithms(controller); + ReadableStreamClose(stream); +} + function ReadableByteStreamControllerCommitPullIntoDescriptor(stream, pullIntoDescriptor) { assert(stream._state !== 'errored'); @@ -1658,11 +994,65 @@ function ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescripto pullIntoDescriptor.buffer, pullIntoDescriptor.byteOffset, bytesFilled / elementSize); } +function ReadableByteStreamControllerEnqueue(controller, chunk) { + const stream = controller._controlledReadableStream; + + if (controller._closeRequest === true || stream._state !== 'readable') { + return; + } + + const buffer = chunk.buffer; + const byteOffset = chunk.byteOffset; + const byteLength = chunk.byteLength; + const transferredBuffer = TransferArrayBuffer(buffer); + + if (ReadableStreamHasDefaultReader(stream) === true) { + if (ReadableStreamGetNumReadRequests(stream) === 0) { + ReadableByteStreamControllerEnqueueChunkToQueue(controller, transferredBuffer, byteOffset, byteLength); + } else { + assert(controller._queue.length === 0); + + const transferredView = new Uint8Array(transferredBuffer, byteOffset, byteLength); + ReadableStreamFulfillReadRequest(stream, transferredView, false); + } + } else if (ReadableStreamHasBYOBReader(stream) === true) { + // TODO: Ideally in this branch detaching should happen only if the buffer is not consumed fully. + ReadableByteStreamControllerEnqueueChunkToQueue(controller, transferredBuffer, byteOffset, byteLength); + ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller); + } else { + assert(IsReadableStreamLocked(stream) === false); + ReadableByteStreamControllerEnqueueChunkToQueue(controller, transferredBuffer, byteOffset, byteLength); + } + + ReadableByteStreamControllerCallPullIfNeeded(controller); +} + function ReadableByteStreamControllerEnqueueChunkToQueue(controller, buffer, byteOffset, byteLength) { controller._queue.push({ buffer, byteOffset, byteLength }); controller._queueTotalSize += byteLength; } +function ReadableByteStreamControllerError(controller, e) { + const stream = controller._controlledReadableStream; + + if (stream._state !== 'readable') { + return; + } + + ReadableByteStreamControllerClearPendingPullIntos(controller); + + ResetQueue(controller); + ReadableByteStreamControllerClearAlgorithms(controller); + ReadableStreamError(stream, e); +} + +function ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, size, pullIntoDescriptor) { + assert(controller._pendingPullIntos.length === 0 || controller._pendingPullIntos[0] === pullIntoDescriptor); + + ReadableByteStreamControllerInvalidateBYOBRequest(controller); + pullIntoDescriptor.bytesFilled += size; +} + function ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, pullIntoDescriptor) { const elementSize = pullIntoDescriptor.elementSize; @@ -1688,7 +1078,7 @@ function ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, const bytesToCopy = Math.min(totalBytesToCopyRemaining, headOfQueue.byteLength); const destStart = pullIntoDescriptor.byteOffset + pullIntoDescriptor.bytesFilled; - ArrayBufferCopy(pullIntoDescriptor.buffer, destStart, headOfQueue.buffer, headOfQueue.byteOffset, bytesToCopy); + CopyDataBlockBytes(pullIntoDescriptor.buffer, destStart, headOfQueue.buffer, headOfQueue.byteOffset, bytesToCopy); if (headOfQueue.byteLength === bytesToCopy) { queue.shift(); @@ -1712,32 +1102,39 @@ function ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, return ready; } -function ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, size, pullIntoDescriptor) { - assert(controller._pendingPullIntos.length === 0 || controller._pendingPullIntos[0] === pullIntoDescriptor); +function ReadableByteStreamControllerGetDesiredSize(controller) { + const stream = controller._controlledReadableStream; + const state = stream._state; - ReadableByteStreamControllerInvalidateBYOBRequest(controller); - pullIntoDescriptor.bytesFilled += size; + if (state === 'errored') { + return null; + } + if (state === 'closed') { + return 0; + } + + return controller._strategyHWM - controller._queueTotalSize; } function ReadableByteStreamControllerHandleQueueDrain(controller) { - assert(controller._controlledReadableByteStream._state === 'readable'); + assert(controller._controlledReadableStream._state === 'readable'); if (controller._queueTotalSize === 0 && controller._closeRequested === true) { ReadableByteStreamControllerClearAlgorithms(controller); - ReadableStreamClose(controller._controlledReadableByteStream); + ReadableStreamClose(controller._controlledReadableStream); } else { ReadableByteStreamControllerCallPullIfNeeded(controller); } } function ReadableByteStreamControllerInvalidateBYOBRequest(controller) { - if (controller._byobRequest === undefined) { + if (controller._byobRequest === null) { return; } - controller._byobRequest._associatedReadableByteStreamController = undefined; - controller._byobRequest._view = undefined; - controller._byobRequest = undefined; + controller._byobRequest._controller = undefined; + controller._byobRequest._view = null; + controller._byobRequest = null; } function ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller) { @@ -1754,7 +1151,7 @@ function ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(contro ReadableByteStreamControllerShiftPendingPullInto(controller); ReadableByteStreamControllerCommitPullIntoDescriptor( - controller._controlledReadableByteStream, + controller._controlledReadableStream, pullIntoDescriptor ); } @@ -1762,7 +1159,7 @@ function ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(contro } function ReadableByteStreamControllerPullInto(controller, view) { - const stream = controller._controlledReadableByteStream; + const stream = controller._controlledReadableStream; let elementSize = 1; if (view.constructor !== DataView) { @@ -1823,12 +1220,18 @@ function ReadableByteStreamControllerPullInto(controller, view) { return promise; } +function ReadableByteStreamControllerRespond(controller, bytesWritten) { + assert(controller._pendingPullIntos.length > 0); + + ReadableByteStreamControllerRespondInternal(controller, bytesWritten); +} + function ReadableByteStreamControllerRespondInClosedState(controller, firstDescriptor) { firstDescriptor.buffer = TransferArrayBuffer(firstDescriptor.buffer); assert(firstDescriptor.bytesFilled === 0); - const stream = controller._controlledReadableByteStream; + const stream = controller._controlledReadableStream; if (ReadableStreamHasBYOBReader(stream) === true) { while (ReadableStreamGetNumReadIntoRequests(stream) > 0) { const pullIntoDescriptor = ReadableByteStreamControllerShiftPendingPullInto(controller); @@ -1860,7 +1263,7 @@ function ReadableByteStreamControllerRespondInReadableState(controller, bytesWri pullIntoDescriptor.buffer = TransferArrayBuffer(pullIntoDescriptor.buffer); pullIntoDescriptor.bytesFilled -= remainderSize; - ReadableByteStreamControllerCommitPullIntoDescriptor(controller._controlledReadableByteStream, pullIntoDescriptor); + ReadableByteStreamControllerCommitPullIntoDescriptor(controller._controlledReadableStream, pullIntoDescriptor); ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller); } @@ -1868,7 +1271,7 @@ function ReadableByteStreamControllerRespondInReadableState(controller, bytesWri function ReadableByteStreamControllerRespondInternal(controller, bytesWritten) { const firstDescriptor = controller._pendingPullIntos[0]; - const stream = controller._controlledReadableByteStream; + const stream = controller._controlledReadableStream; if (stream._state === 'closed') { if (bytesWritten !== 0) { @@ -1885,6 +1288,23 @@ function ReadableByteStreamControllerRespondInternal(controller, bytesWritten) { ReadableByteStreamControllerCallPullIfNeeded(controller); } +function ReadableByteStreamControllerRespondWithNewView(controller, view) { + assert(controller._pendingPullIntos.length > 0); + + const firstDescriptor = controller._pendingPullIntos[0]; + + if (firstDescriptor.byteOffset + firstDescriptor.bytesFilled !== view.byteOffset) { + throw new RangeError('The region specified by view does not match byobRequest'); + } + if (firstDescriptor.byteLength !== view.byteLength) { + throw new RangeError('The buffer of view has different capacity than byobRequest'); + } + + firstDescriptor.buffer = view.buffer; + + ReadableByteStreamControllerRespondInternal(controller, view.byteLength); +} + function ReadableByteStreamControllerShiftPendingPullInto(controller) { const descriptor = controller._pendingPullIntos.shift(); ReadableByteStreamControllerInvalidateBYOBRequest(controller); @@ -1892,7 +1312,7 @@ function ReadableByteStreamControllerShiftPendingPullInto(controller) { } function ReadableByteStreamControllerShouldCallPull(controller) { - const stream = controller._controlledReadableByteStream; + const stream = controller._controlledReadableStream; if (stream._state !== 'readable') { return false; @@ -1923,127 +1343,6 @@ function ReadableByteStreamControllerShouldCallPull(controller) { return false; } -function ReadableByteStreamControllerClearAlgorithms(controller) { - controller._pullAlgorithm = undefined; - controller._cancelAlgorithm = undefined; -} - -function ReadableByteStreamControllerClose(controller) { - const stream = controller._controlledReadableByteStream; - - if (controller._closeRequest === true || stream._state !== 'readable') { - return; - } - - if (controller._queueTotalSize > 0) { - controller._closeRequested = true; - - return; - } - - if (controller._pendingPullIntos.length > 0) { - const firstPendingPullInto = controller._pendingPullIntos[0]; - if (firstPendingPullInto.bytesFilled > 0) { - const e = new TypeError('Insufficient bytes to fill elements in the given buffer'); - ReadableByteStreamControllerError(controller, e); - - throw e; - } - } - - ReadableByteStreamControllerClearAlgorithms(controller); - ReadableStreamClose(stream); -} - -function ReadableByteStreamControllerEnqueue(controller, chunk) { - const stream = controller._controlledReadableByteStream; - - if (controller._closeRequest === true || stream._state !== 'readable') { - return; - } - - const buffer = chunk.buffer; - const byteOffset = chunk.byteOffset; - const byteLength = chunk.byteLength; - const transferredBuffer = TransferArrayBuffer(buffer); - - if (ReadableStreamHasDefaultReader(stream) === true) { - if (ReadableStreamGetNumReadRequests(stream) === 0) { - ReadableByteStreamControllerEnqueueChunkToQueue(controller, transferredBuffer, byteOffset, byteLength); - } else { - assert(controller._queue.length === 0); - - const transferredView = new Uint8Array(transferredBuffer, byteOffset, byteLength); - ReadableStreamFulfillReadRequest(stream, transferredView, false); - } - } else if (ReadableStreamHasBYOBReader(stream) === true) { - // TODO: Ideally in this branch detaching should happen only if the buffer is not consumed fully. - ReadableByteStreamControllerEnqueueChunkToQueue(controller, transferredBuffer, byteOffset, byteLength); - ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller); - } else { - assert(IsReadableStreamLocked(stream) === false); - ReadableByteStreamControllerEnqueueChunkToQueue(controller, transferredBuffer, byteOffset, byteLength); - } - - ReadableByteStreamControllerCallPullIfNeeded(controller); -} - -function ReadableByteStreamControllerError(controller, e) { - const stream = controller._controlledReadableByteStream; - - if (stream._state !== 'readable') { - return; - } - - ReadableByteStreamControllerClearPendingPullIntos(controller); - - ResetQueue(controller); - ReadableByteStreamControllerClearAlgorithms(controller); - ReadableStreamError(stream, e); -} - -function ReadableByteStreamControllerGetDesiredSize(controller) { - const stream = controller._controlledReadableByteStream; - const state = stream._state; - - if (state === 'errored') { - return null; - } - if (state === 'closed') { - return 0; - } - - return controller._strategyHWM - controller._queueTotalSize; -} - -function ReadableByteStreamControllerRespond(controller, bytesWritten) { - bytesWritten = Number(bytesWritten); - if (IsFiniteNonNegativeNumber(bytesWritten) === false) { - throw new RangeError('bytesWritten must be a finite'); - } - - assert(controller._pendingPullIntos.length > 0); - - ReadableByteStreamControllerRespondInternal(controller, bytesWritten); -} - -function ReadableByteStreamControllerRespondWithNewView(controller, view) { - assert(controller._pendingPullIntos.length > 0); - - const firstDescriptor = controller._pendingPullIntos[0]; - - if (firstDescriptor.byteOffset + firstDescriptor.bytesFilled !== view.byteOffset) { - throw new RangeError('The region specified by view does not match byobRequest'); - } - if (firstDescriptor.byteLength !== view.byteLength) { - throw new RangeError('The buffer of view has different capacity than byobRequest'); - } - - firstDescriptor.buffer = view.buffer; - - ReadableByteStreamControllerRespondInternal(controller, view.byteLength); -} - function SetUpReadableByteStreamController(stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark, autoAllocateChunkSize) { assert(stream._readableStreamController === undefined); @@ -2052,12 +1351,12 @@ function SetUpReadableByteStreamController(stream, controller, startAlgorithm, p assert(autoAllocateChunkSize > 0); } - controller._controlledReadableByteStream = stream; + controller._controlledReadableStream = stream; controller._pullAgain = false; controller._pulling = false; - controller._byobRequest = undefined; + controller._byobRequest = null; // Need to set the slots so that the assert doesn't fire. In the spec the slots already exist implicitly. controller._queue = controller._queueTotalSize = undefined; @@ -2066,7 +1365,7 @@ function SetUpReadableByteStreamController(stream, controller, startAlgorithm, p controller._closeRequested = false; controller._started = false; - controller._strategyHWM = ValidateAndNormalizeHighWaterMark(highWaterMark); + controller._strategyHWM = highWaterMark; controller._pullAlgorithm = pullAlgorithm; controller._cancelAlgorithm = cancelAlgorithm; @@ -2094,145 +1393,27 @@ function SetUpReadableByteStreamController(stream, controller, startAlgorithm, p ); } -function SetUpReadableByteStreamControllerFromUnderlyingSource(stream, underlyingByteSource, highWaterMark) { - assert(underlyingByteSource !== undefined); +function SetUpReadableByteStreamControllerFromUnderlyingSource( + stream, underlyingSource, underlyingSourceDict, highWaterMark) { + const controller = ReadableByteStreamController.new(globalThis); - const controller = Object.create(ReadableByteStreamController.prototype); + let startAlgorithm = () => undefined; + let pullAlgorithm = () => promiseResolvedWith(undefined); + let cancelAlgorithm = () => promiseResolvedWith(undefined); - function startAlgorithm() { - return InvokeOrNoop(underlyingByteSource, 'start', [controller]); + if ('start' in underlyingSourceDict) { + startAlgorithm = () => underlyingSourceDict.start.call(underlyingSource, controller); } - - const pullAlgorithm = CreateAlgorithmFromUnderlyingMethod(underlyingByteSource, 'pull', 0, [controller]); - const cancelAlgorithm = CreateAlgorithmFromUnderlyingMethod(underlyingByteSource, 'cancel', 1, []); - - let autoAllocateChunkSize = underlyingByteSource.autoAllocateChunkSize; - if (autoAllocateChunkSize !== undefined) { - autoAllocateChunkSize = Number(autoAllocateChunkSize); - if (Number.isInteger(autoAllocateChunkSize) === false || autoAllocateChunkSize <= 0) { - throw new RangeError('autoAllocateChunkSize must be a positive integer'); - } + if ('pull' in underlyingSourceDict) { + pullAlgorithm = () => underlyingSourceDict.pull.call(underlyingSource, controller); } - - SetUpReadableByteStreamController(stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark, - autoAllocateChunkSize); -} - -function SetUpReadableStreamBYOBRequest(request, controller, view) { - assert(IsReadableByteStreamController(controller) === true); - assert(typeof view === 'object'); - assert(ArrayBuffer.isView(view) === true); - assert(IsDetachedBuffer(view.buffer) === false); - request._associatedReadableByteStreamController = controller; - request._view = view; -} - -// Helper functions for the ReadableStream. - -function isAbortSignal(value) { - if (typeof value !== 'object' || value === null) { - return false; + if ('cancel' in underlyingSourceDict) { + cancelAlgorithm = reason => underlyingSourceDict.cancel.call(underlyingSource, reason); } - // Use the brand check to distinguish a real AbortSignal from a fake one. - const aborted = Object.getOwnPropertyDescriptor(AbortSignal.prototype, 'aborted').get; - try { - aborted.call(value); - return true; - } catch (e) { - return false; - } -} - -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) { - return new TypeError('Cannot ' + name + ' a stream using a released reader'); -} - -// Helper functions for the ReadableStreamDefaultReader. - -function defaultReaderBrandCheckException(name) { - return new TypeError( - `ReadableStreamDefaultReader.prototype.${name} can only be used on a ReadableStreamDefaultReader`); -} - -function defaultReaderClosedPromiseInitialize(reader) { - reader._closedPromise = newPromise((resolve, reject) => { - reader._closedPromise_resolve = resolve; - reader._closedPromise_reject = reject; - }); -} - -function defaultReaderClosedPromiseInitializeAsRejected(reader, reason) { - reader._closedPromise = promiseRejectedWith(reason); - reader._closedPromise_resolve = undefined; - reader._closedPromise_reject = undefined; -} - -function defaultReaderClosedPromiseInitializeAsResolved(reader) { - reader._closedPromise = promiseResolvedWith(undefined); - reader._closedPromise_resolve = undefined; - reader._closedPromise_reject = undefined; -} - -function defaultReaderClosedPromiseReject(reader, reason) { - assert(reader._closedPromise_resolve !== undefined); - assert(reader._closedPromise_reject !== undefined); - - reader._closedPromise_reject(reason); - reader._closedPromise_resolve = undefined; - reader._closedPromise_reject = undefined; -} - -function defaultReaderClosedPromiseResetToRejected(reader, reason) { - assert(reader._closedPromise_resolve === undefined); - assert(reader._closedPromise_reject === undefined); - - reader._closedPromise = promiseRejectedWith(reason); -} - -function defaultReaderClosedPromiseResolve(reader) { - assert(reader._closedPromise_resolve !== undefined); - assert(reader._closedPromise_reject !== undefined); + const autoAllocateChunkSize = underlyingSourceDict.autoAllocateChunkSize; - reader._closedPromise_resolve(undefined); - reader._closedPromise_resolve = undefined; - reader._closedPromise_reject = undefined; -} - -// Helper functions for the ReadableStreamDefaultReader. - -function byobReaderBrandCheckException(name) { - return new TypeError( - `ReadableStreamBYOBReader.prototype.${name} can only be used on a ReadableStreamBYOBReader`); -} - -// Helper functions for the ReadableStreamDefaultController. - -function defaultControllerBrandCheckException(name) { - return new TypeError( - `ReadableStreamDefaultController.prototype.${name} can only be used on a ReadableStreamDefaultController`); -} - -// Helper functions for the ReadableStreamBYOBRequest. - -function byobRequestBrandCheckException(name) { - return new TypeError( - `ReadableStreamBYOBRequest.prototype.${name} can only be used on a ReadableStreamBYOBRequest`); -} - -// Helper functions for the ReadableByteStreamController. - -function byteStreamControllerBrandCheckException(name) { - return new TypeError( - `ReadableByteStreamController.prototype.${name} can only be used on a ReadableByteStreamController`); + SetUpReadableByteStreamController( + stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark, autoAllocateChunkSize + ); } diff --git a/reference-implementation/lib/transform-stream.js b/reference-implementation/lib/abstract-ops/transform-streams.js similarity index 51% rename from reference-implementation/lib/transform-stream.js rename to reference-implementation/lib/abstract-ops/transform-streams.js index f496a7b13..2affc0cb1 100644 --- a/reference-implementation/lib/transform-stream.js +++ b/reference-implementation/lib/abstract-ops/transform-streams.js @@ -1,110 +1,31 @@ 'use strict'; const assert = require('assert'); - -// Calls to verbose() are purely for debugging the reference implementation and tests. They are not part of the standard -// and do not appear in the standard text. const verbose = require('debug')('streams:transform-stream:verbose'); -const { InvokeOrNoop, CreateAlgorithmFromUnderlyingMethod, PromiseCall, typeIsObject, - ValidateAndNormalizeHighWaterMark, IsNonNegativeNumber, MakeSizeAlgorithmFromSizeFunction, newPromise, - promiseResolvedWith, promiseRejectedWith, transformPromiseWith } = require('./helpers.js'); -const { CreateReadableStream, ReadableStreamDefaultControllerClose, ReadableStreamDefaultControllerEnqueue, - ReadableStreamDefaultControllerError, ReadableStreamDefaultControllerGetDesiredSize, - ReadableStreamDefaultControllerHasBackpressure, - ReadableStreamDefaultControllerCanCloseOrEnqueue } = require('./readable-stream.js'); -const { CreateWritableStream, WritableStreamDefaultControllerErrorIfNeeded } = require('./writable-stream.js'); - -// Class TransformStream - -class TransformStream { - constructor(transformer = {}, writableStrategy = {}, readableStrategy = {}) { - const writableSizeFunction = writableStrategy.size; - let writableHighWaterMark = writableStrategy.highWaterMark; - const readableSizeFunction = readableStrategy.size; - let readableHighWaterMark = readableStrategy.highWaterMark; - - const writableType = transformer.writableType; - - if (writableType !== undefined) { - throw new RangeError('Invalid writable type specified'); - } - - const writableSizeAlgorithm = MakeSizeAlgorithmFromSizeFunction(writableSizeFunction); - if (writableHighWaterMark === undefined) { - writableHighWaterMark = 1; - } - writableHighWaterMark = ValidateAndNormalizeHighWaterMark(writableHighWaterMark); - - const readableType = transformer.readableType; - - if (readableType !== undefined) { - throw new RangeError('Invalid readable type specified'); - } - - const readableSizeAlgorithm = MakeSizeAlgorithmFromSizeFunction(readableSizeFunction); - if (readableHighWaterMark === undefined) { - readableHighWaterMark = 0; - } - readableHighWaterMark = ValidateAndNormalizeHighWaterMark(readableHighWaterMark); - - let startPromise_resolve; - const startPromise = newPromise(resolve => { - startPromise_resolve = resolve; - }); - - InitializeTransformStream(this, startPromise, writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, - readableSizeAlgorithm); - SetUpTransformStreamDefaultControllerFromTransformer(this, transformer); - - const startResult = InvokeOrNoop(transformer, 'start', [this._transformStreamController]); - startPromise_resolve(startResult); - } - - get readable() { - if (IsTransformStream(this) === false) { - throw streamBrandCheckException('readable'); - } - - return this._readable; - } - - get writable() { - if (IsTransformStream(this) === false) { - throw streamBrandCheckException('writable'); - } - - return this._writable; - } -} - -// Transform Stream Abstract Operations - -function CreateTransformStream(startAlgorithm, transformAlgorithm, flushAlgorithm, writableHighWaterMark = 1, - writableSizeAlgorithm = () => 1, readableHighWaterMark = 0, - readableSizeAlgorithm = () => 1) { - assert(IsNonNegativeNumber(writableHighWaterMark)); - assert(IsNonNegativeNumber(readableHighWaterMark)); - const stream = Object.create(TransformStream.prototype); +const { promiseResolvedWith, promiseRejectedWith, newPromise, resolvePromise, transformPromiseWith } = + require('../helpers/webidl.js'); +const { CreateReadableStream, ReadableStreamDefaultControllerClose, ReadableStreamDefaultControllerEnqueue, + ReadableStreamDefaultControllerError, ReadableStreamDefaultControllerHasBackpressure, + ReadableStreamDefaultControllerCanCloseOrEnqueue } = require('./readable-streams.js'); +const { CreateWritableStream, WritableStreamDefaultControllerErrorIfNeeded } = require('./writable-streams.js'); - let startPromise_resolve; - const startPromise = newPromise(resolve => { - startPromise_resolve = resolve; - }); +const TransformStream = require('../../generated/TransformStream.js'); +const TransformStreamDefaultController = require('../../generated/TransformStreamDefaultController.js'); - InitializeTransformStream(stream, startPromise, writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, - readableSizeAlgorithm); +Object.assign(exports, { + InitializeTransformStream, + SetUpTransformStreamDefaultControllerFromTransformer, + TransformStreamDefaultControllerEnqueue, + TransformStreamDefaultControllerError, + TransformStreamDefaultControllerTerminate +}); - const controller = Object.create(TransformStreamDefaultController.prototype); +// Working with transform streams - SetUpTransformStreamDefaultController(stream, controller, transformAlgorithm, flushAlgorithm); - - const startResult = startAlgorithm(); - startPromise_resolve(startResult); - return stream; -} +// CreateTransformStream is not implemented since it is only meant for external specs. -function InitializeTransformStream(stream, startPromise, writableHighWaterMark, writableSizeAlgorithm, - readableHighWaterMark, readableSizeAlgorithm) { +function InitializeTransformStream( + stream, startPromise, writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm) { function startAlgorithm() { return startPromise; } @@ -121,8 +42,9 @@ function InitializeTransformStream(stream, startPromise, writableHighWaterMark, return TransformStreamDefaultSinkCloseAlgorithm(stream); } - stream._writable = CreateWritableStream(startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, - writableHighWaterMark, writableSizeAlgorithm); + stream._writable = CreateWritableStream( + startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, writableHighWaterMark, writableSizeAlgorithm + ); function pullAlgorithm() { return TransformStreamDefaultSourcePullAlgorithm(stream); @@ -133,32 +55,18 @@ function InitializeTransformStream(stream, startPromise, writableHighWaterMark, return promiseResolvedWith(undefined); } - stream._readable = CreateReadableStream(startAlgorithm, pullAlgorithm, cancelAlgorithm, readableHighWaterMark, - readableSizeAlgorithm); + stream._readable = CreateReadableStream( + startAlgorithm, pullAlgorithm, cancelAlgorithm, readableHighWaterMark, readableSizeAlgorithm + ); // The [[backpressure]] slot is set to undefined so that it can be initialised by TransformStreamSetBackpressure. stream._backpressure = undefined; stream._backpressureChangePromise = undefined; - stream._backpressureChangePromise_resolve = undefined; TransformStreamSetBackpressure(stream, true); - // Used by IsWritableStream() which is called by SetUpTransformStreamDefaultController(). stream._transformStreamController = undefined; } -function IsTransformStream(x) { - if (!typeIsObject(x)) { - return false; - } - - if (!Object.prototype.hasOwnProperty.call(x, '_transformStreamController')) { - return false; - } - - return true; -} - -// This is a no-op if both sides are already errored. function TransformStreamError(stream, e) { verbose('TransformStreamError()'); @@ -184,73 +92,18 @@ function TransformStreamSetBackpressure(stream, backpressure) { assert(stream._backpressure !== backpressure); if (stream._backpressureChangePromise !== undefined) { - stream._backpressureChangePromise_resolve(); + resolvePromise(stream._backpressureChangePromise, undefined); } - stream._backpressureChangePromise = newPromise(resolve => { - stream._backpressureChangePromise_resolve = resolve; - }); + stream._backpressureChangePromise = newPromise(); stream._backpressure = backpressure; } -// Class TransformStreamDefaultController - -class TransformStreamDefaultController { - constructor() { - throw new TypeError('TransformStreamDefaultController instances cannot be created directly'); - } - - get desiredSize() { - if (IsTransformStreamDefaultController(this) === false) { - throw defaultControllerBrandCheckException('desiredSize'); - } - - const readableController = this._controlledTransformStream._readable._readableStreamController; - return ReadableStreamDefaultControllerGetDesiredSize(readableController); - } - - enqueue(chunk) { - if (IsTransformStreamDefaultController(this) === false) { - throw defaultControllerBrandCheckException('enqueue'); - } - - TransformStreamDefaultControllerEnqueue(this, chunk); - } - - error(reason) { - if (IsTransformStreamDefaultController(this) === false) { - throw defaultControllerBrandCheckException('error'); - } - - TransformStreamDefaultControllerError(this, reason); - } - - terminate() { - if (IsTransformStreamDefaultController(this) === false) { - throw defaultControllerBrandCheckException('terminate'); - } - - TransformStreamDefaultControllerTerminate(this); - } -} - -// Transform Stream Default Controller Abstract Operations - -function IsTransformStreamDefaultController(x) { - if (!typeIsObject(x)) { - return false; - } - - if (!Object.prototype.hasOwnProperty.call(x, '_controlledTransformStream')) { - return false; - } - - return true; -} +// Default controllers function SetUpTransformStreamDefaultController(stream, controller, transformAlgorithm, flushAlgorithm) { - assert(IsTransformStream(stream) === true); + assert(TransformStream.isImpl(stream)); assert(stream._transformStreamController === undefined); controller._controlledTransformStream = stream; @@ -260,10 +113,10 @@ function SetUpTransformStreamDefaultController(stream, controller, transformAlgo controller._flushAlgorithm = flushAlgorithm; } -function SetUpTransformStreamDefaultControllerFromTransformer(stream, transformer) { +function SetUpTransformStreamDefaultControllerFromTransformer(stream, transformer, transformerDict) { assert(transformer !== undefined); - const controller = Object.create(TransformStreamDefaultController.prototype); + const controller = TransformStreamDefaultController.new(globalThis); let transformAlgorithm = chunk => { try { @@ -273,15 +126,15 @@ function SetUpTransformStreamDefaultControllerFromTransformer(stream, transforme return promiseRejectedWith(transformResultE); } }; - const transformMethod = transformer.transform; - if (transformMethod !== undefined) { - if (typeof transformMethod !== 'function') { - throw new TypeError('transform is not a method'); - } - transformAlgorithm = chunk => PromiseCall(transformMethod, transformer, [chunk, controller]); - } - const flushAlgorithm = CreateAlgorithmFromUnderlyingMethod(transformer, 'flush', 0, [controller]); + let flushAlgorithm = () => promiseResolvedWith(undefined); + + if ('transform' in transformerDict) { + transformAlgorithm = chunk => transformerDict.transform.call(transformer, chunk, controller); + } + if ('flush' in transformerDict) { + flushAlgorithm = () => transformerDict.flush.call(transformer, controller); + } SetUpTransformStreamDefaultController(stream, controller, transformAlgorithm, flushAlgorithm); } @@ -343,7 +196,7 @@ function TransformStreamDefaultControllerTerminate(controller) { TransformStreamErrorWritableAndUnblockWrite(stream, error); } -// TransformStreamDefaultSink Algorithms +// Default sinks function TransformStreamDefaultSinkWriteAlgorithm(stream, chunk) { verbose('TransformStreamDefaultSinkWriteAlgorithm()'); @@ -401,7 +254,7 @@ function TransformStreamDefaultSinkCloseAlgorithm(stream) { }); } -// TransformStreamDefaultSource Algorithms +// Default sources function TransformStreamDefaultSourcePullAlgorithm(stream) { verbose('TransformStreamDefaultSourcePullAlgorithm()'); @@ -416,19 +269,3 @@ function TransformStreamDefaultSourcePullAlgorithm(stream) { // Prevent the next pull() call until there is backpressure. return stream._backpressureChangePromise; } - -module.exports = { CreateTransformStream, TransformStream }; - -// Helper functions for the TransformStreamDefaultController. - -function defaultControllerBrandCheckException(name) { - return new TypeError( - `TransformStreamDefaultController.prototype.${name} can only be used on a TransformStreamDefaultController`); -} - -// Helper functions for the TransformStream. - -function streamBrandCheckException(name) { - return new TypeError( - `TransformStream.prototype.${name} can only be used on a TransformStream`); -} diff --git a/reference-implementation/lib/writable-stream.js b/reference-implementation/lib/abstract-ops/writable-streams.js similarity index 53% rename from reference-implementation/lib/writable-stream.js rename to reference-implementation/lib/abstract-ops/writable-streams.js index c221c9e1c..e77cb36d8 100644 --- a/reference-implementation/lib/writable-stream.js +++ b/reference-implementation/lib/abstract-ops/writable-streams.js @@ -1,115 +1,54 @@ 'use strict'; const assert = require('assert'); - -// Calls to verbose() are purely for debugging the reference implementation and tests. They are not part of the standard -// and do not appear in the standard text. const verbose = require('debug')('streams:writable-stream:verbose'); -const { CreateAlgorithmFromUnderlyingMethod, InvokeOrNoop, ValidateAndNormalizeHighWaterMark, IsNonNegativeNumber, - MakeSizeAlgorithmFromSizeFunction, typeIsObject, newPromise, promiseResolvedWith, promiseRejectedWith, - uponPromise, setPromiseIsHandledToTrue } = require('./helpers.js'); +const { promiseResolvedWith, promiseRejectedWith, newPromise, resolvePromise, rejectPromise, uponPromise, + setPromiseIsHandledToTrue, stateIsPending } = require('../helpers/webidl.js'); +const { IsNonNegativeNumber } = require('./miscellaneous.js'); const { DequeueValue, EnqueueValueWithSize, PeekQueueValue, ResetQueue } = require('./queue-with-sizes.js'); +const { AbortSteps, ErrorSteps } = require('./internal-methods.js'); -const AbortSteps = Symbol('[[AbortSteps]]'); -const ErrorSteps = Symbol('[[ErrorSteps]]'); - -class WritableStream { - constructor(underlyingSink = {}, strategy = {}) { - InitializeWritableStream(this); - - const size = strategy.size; - let highWaterMark = strategy.highWaterMark; - - const type = underlyingSink.type; - - if (type !== undefined) { - throw new RangeError('Invalid type is specified'); - } - - const sizeAlgorithm = MakeSizeAlgorithmFromSizeFunction(size); - if (highWaterMark === undefined) { - highWaterMark = 1; - } - highWaterMark = ValidateAndNormalizeHighWaterMark(highWaterMark); - - SetUpWritableStreamDefaultControllerFromUnderlyingSink(this, underlyingSink, highWaterMark, sizeAlgorithm); - } - - get locked() { - if (IsWritableStream(this) === false) { - throw streamBrandCheckException('locked'); - } - - return IsWritableStreamLocked(this); - } - - abort(reason) { - if (IsWritableStream(this) === false) { - return promiseRejectedWith(streamBrandCheckException('abort')); - } - - if (IsWritableStreamLocked(this) === true) { - return promiseRejectedWith(new TypeError('Cannot abort a stream that already has a writer')); - } - - return WritableStreamAbort(this, reason); - } - - close() { - if (IsWritableStream(this) === false) { - return promiseRejectedWith(streamBrandCheckException('close')); - } - - if (IsWritableStreamLocked(this) === true) { - return promiseRejectedWith(new TypeError('Cannot close a stream that already has a writer')); - } - - if (WritableStreamCloseQueuedOrInFlight(this) === true) { - return promiseRejectedWith(new TypeError('Cannot close an already-closing stream')); - } - - return WritableStreamClose(this); - } - - getWriter() { - if (IsWritableStream(this) === false) { - throw streamBrandCheckException('getWriter'); - } - - return AcquireWritableStreamDefaultWriter(this); - } -} +const WritableStream = require('../../generated/WritableStream.js'); +const WritableStreamDefaultController = require('../../generated/WritableStreamDefaultController.js'); +const WritableStreamDefaultWriter = require('../../generated/WritableStreamDefaultWriter.js'); -module.exports = { +Object.assign(exports, { AcquireWritableStreamDefaultWriter, CreateWritableStream, - IsWritableStream, + InitializeWritableStream, IsWritableStreamLocked, - WritableStream, + SetUpWritableStreamDefaultControllerFromUnderlyingSink, + SetUpWritableStreamDefaultWriter, WritableStreamAbort, WritableStreamClose, + WritableStreamCloseQueuedOrInFlight, + WritableStreamDefaultControllerClearAlgorithms, + WritableStreamDefaultControllerError, WritableStreamDefaultControllerErrorIfNeeded, + WritableStreamDefaultWriterAbort, + WritableStreamDefaultWriterClose, WritableStreamDefaultWriterCloseWithErrorPropagation, + WritableStreamDefaultWriterGetDesiredSize, WritableStreamDefaultWriterRelease, - WritableStreamDefaultWriterWrite, - WritableStreamCloseQueuedOrInFlight -}; + WritableStreamDefaultWriterWrite +}); -// Abstract operations for the WritableStream. +// Working with writable streams function AcquireWritableStreamDefaultWriter(stream) { - return new WritableStreamDefaultWriter(stream); + const writer = WritableStreamDefaultWriter.new(globalThis); + SetUpWritableStreamDefaultWriter(writer, stream); + return writer; } -// Throws if and only if startAlgorithm throws. function CreateWritableStream(startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, highWaterMark = 1, sizeAlgorithm = () => 1) { assert(IsNonNegativeNumber(highWaterMark) === true); - const stream = Object.create(WritableStream.prototype); + const stream = WritableStream.new(globalThis); InitializeWritableStream(stream); - const controller = Object.create(WritableStreamDefaultController.prototype); + const controller = WritableStreamDefaultController.new(globalThis); SetUpWritableStreamDefaultController(stream, controller, startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, highWaterMark, sizeAlgorithm); @@ -129,10 +68,6 @@ function InitializeWritableStream(stream) { // variable to validate the caller. stream._writableStreamController = undefined; - // This queue is placed here instead of the writer class in order to allow for passing a writer to the next data - // producer without waiting for the queued writes to finish. - stream._writeRequests = []; - // Write requests are removed from _writeRequests when write() is called on the underlying sink. This prevents // them from being erroneously rejected on error. If a write() call is in-flight, the request is stored here. stream._inFlightWriteRequest = undefined; @@ -148,24 +83,16 @@ function InitializeWritableStream(stream) { // The promise that was returned from writer.abort(). This may also be fulfilled after the writer has detached. stream._pendingAbortRequest = undefined; + // This queue is placed here instead of the writer class in order to allow for passing a writer to the next data + // producer without waiting for the queued writes to finish. + stream._writeRequests = []; + // The backpressure signal set by the controller. stream._backpressure = false; } -function IsWritableStream(x) { - if (!typeIsObject(x)) { - return false; - } - - if (!Object.prototype.hasOwnProperty.call(x, '_writableStreamController')) { - return false; - } - - return true; -} - function IsWritableStreamLocked(stream) { - assert(IsWritableStream(stream) === true); + assert(WritableStream.isImpl(stream)); if (stream._writer === undefined) { return false; @@ -180,7 +107,7 @@ function WritableStreamAbort(stream, reason) { return promiseResolvedWith(undefined); } if (stream._pendingAbortRequest !== undefined) { - return stream._pendingAbortRequest._promise; + return stream._pendingAbortRequest.promise; } assert(state === 'writable' || state === 'erroring'); @@ -192,15 +119,8 @@ function WritableStreamAbort(stream, reason) { reason = undefined; } - const promise = newPromise((resolve, reject) => { - stream._pendingAbortRequest = { - _resolve: resolve, - _reject: reject, - _reason: reason, - _wasAlreadyErroring: wasAlreadyErroring - }; - }); - stream._pendingAbortRequest._promise = promise; + const promise = newPromise(); + stream._pendingAbortRequest = { promise, reason, wasAlreadyErroring }; if (wasAlreadyErroring === false) { WritableStreamStartErroring(stream, reason); @@ -209,6 +129,42 @@ function WritableStreamAbort(stream, reason) { return promise; } +function SetUpWritableStreamDefaultWriter(writer, stream) { + if (IsWritableStreamLocked(stream) === true) { + throw new TypeError('This stream has already been locked for exclusive writing by another writer'); + } + + writer._ownerWritableStream = stream; + stream._writer = writer; + + const state = stream._state; + + if (state === 'writable') { + if (WritableStreamCloseQueuedOrInFlight(stream) === false && stream._backpressure === true) { + writer._readyPromise = newPromise(); + } else { + writer._readyPromise = promiseResolvedWith(undefined); + } + + writer._closedPromise = newPromise(); + } else if (state === 'erroring') { + writer._readyPromise = promiseRejectedWith(stream._storedError); + setPromiseIsHandledToTrue(writer._readyPromise); + writer._closedPromise = newPromise(); + } else if (state === 'closed') { + writer._readyPromise = promiseResolvedWith(undefined); + writer._closedPromise = promiseResolvedWith(undefined); + } else { + assert(state === 'errored'); + + const storedError = stream._storedError; + writer._readyPromise = promiseRejectedWith(storedError); + setPromiseIsHandledToTrue(writer._readyPromise); + writer._closedPromise = promiseRejectedWith(storedError); + setPromiseIsHandledToTrue(writer._closedPromise); + } +} + function WritableStreamClose(stream) { const state = stream._state; if (state === 'closed' || state === 'errored') { @@ -219,18 +175,12 @@ function WritableStreamClose(stream) { assert(state === 'writable' || state === 'erroring'); assert(WritableStreamCloseQueuedOrInFlight(stream) === false); - const promise = newPromise((resolve, reject) => { - const closeRequest = { - _resolve: resolve, - _reject: reject - }; - - stream._closeRequest = closeRequest; - }); + const promise = newPromise(); + stream._closeRequest = promise; const writer = stream._writer; if (writer !== undefined && stream._backpressure === true && state === 'writable') { - defaultWriterReadyPromiseResolve(writer); + resolvePromise(writer._readyPromise, undefined); } WritableStreamDefaultControllerClose(stream._writableStreamController); @@ -238,22 +188,23 @@ function WritableStreamClose(stream) { return promise; } -// WritableStream API exposed for controllers. +// Interfacing with controllers function WritableStreamAddWriteRequest(stream) { assert(IsWritableStreamLocked(stream) === true); assert(stream._state === 'writable'); - const promise = newPromise((resolve, reject) => { - const writeRequest = { - _resolve: resolve, - _reject: reject - }; + const promise = newPromise(); + stream._writeRequests.push(promise); + return promise; +} - stream._writeRequests.push(writeRequest); - }); +function WritableStreamCloseQueuedOrInFlight(stream) { + if (stream._closeRequest === undefined && stream._inFlightCloseRequest === undefined) { + return false; + } - return promise; + return true; } function WritableStreamDealWithRejection(stream, error) { @@ -269,26 +220,6 @@ function WritableStreamDealWithRejection(stream, error) { WritableStreamFinishErroring(stream); } -function WritableStreamStartErroring(stream, reason) { - verbose('WritableStreamStartErroring(stream, %o)', reason); - assert(stream._storedError === undefined); - assert(stream._state === 'writable'); - - const controller = stream._writableStreamController; - assert(controller !== undefined); - - stream._state = 'erroring'; - stream._storedError = reason; - const writer = stream._writer; - if (writer !== undefined) { - WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason); - } - - if (WritableStreamHasOperationMarkedInFlight(stream) === false && controller._started === true) { - WritableStreamFinishErroring(stream); - } -} - function WritableStreamFinishErroring(stream) { verbose('WritableStreamFinishErroring()'); assert(stream._state === 'erroring'); @@ -298,7 +229,7 @@ function WritableStreamFinishErroring(stream) { const storedError = stream._storedError; for (const writeRequest of stream._writeRequests) { - writeRequest._reject(storedError); + rejectPromise(writeRequest, storedError); } stream._writeRequests = []; @@ -310,44 +241,28 @@ function WritableStreamFinishErroring(stream) { const abortRequest = stream._pendingAbortRequest; stream._pendingAbortRequest = undefined; - if (abortRequest._wasAlreadyErroring === true) { - abortRequest._reject(storedError); + if (abortRequest.wasAlreadyErroring === true) { + rejectPromise(abortRequest.promise, storedError); WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream); return; } - const promise = stream._writableStreamController[AbortSteps](abortRequest._reason); + const promise = stream._writableStreamController[AbortSteps](abortRequest.reason); uponPromise( promise, () => { - abortRequest._resolve(); + resolvePromise(abortRequest.promise); WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream); }, reason => { - abortRequest._reject(reason); + rejectPromise(abortRequest.promise, reason); WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream); }); } -function WritableStreamFinishInFlightWrite(stream) { - assert(stream._inFlightWriteRequest !== undefined); - stream._inFlightWriteRequest._resolve(undefined); - stream._inFlightWriteRequest = undefined; -} - -function WritableStreamFinishInFlightWriteWithError(stream, error) { - assert(stream._inFlightWriteRequest !== undefined); - stream._inFlightWriteRequest._reject(error); - stream._inFlightWriteRequest = undefined; - - assert(stream._state === 'writable' || stream._state === 'erroring'); - - WritableStreamDealWithRejection(stream, error); -} - function WritableStreamFinishInFlightClose(stream) { assert(stream._inFlightCloseRequest !== undefined); - stream._inFlightCloseRequest._resolve(undefined); + resolvePromise(stream._inFlightCloseRequest, undefined); stream._inFlightCloseRequest = undefined; const state = stream._state; @@ -358,7 +273,7 @@ function WritableStreamFinishInFlightClose(stream) { // The error was too late to do anything, so it is ignored. stream._storedError = undefined; if (stream._pendingAbortRequest !== undefined) { - stream._pendingAbortRequest._resolve(); + resolvePromise(stream._pendingAbortRequest.promise, undefined); stream._pendingAbortRequest = undefined; } } @@ -367,7 +282,7 @@ function WritableStreamFinishInFlightClose(stream) { const writer = stream._writer; if (writer !== undefined) { - defaultWriterClosedPromiseResolve(writer); + resolvePromise(writer._closedPromise, undefined); } assert(stream._pendingAbortRequest === undefined); @@ -376,26 +291,33 @@ function WritableStreamFinishInFlightClose(stream) { function WritableStreamFinishInFlightCloseWithError(stream, error) { assert(stream._inFlightCloseRequest !== undefined); - stream._inFlightCloseRequest._reject(error); + rejectPromise(stream._inFlightCloseRequest, error); stream._inFlightCloseRequest = undefined; assert(stream._state === 'writable' || stream._state === 'erroring'); // Never execute sink abort() after sink close(). if (stream._pendingAbortRequest !== undefined) { - stream._pendingAbortRequest._reject(error); + rejectPromise(stream._pendingAbortRequest.promise, error); stream._pendingAbortRequest = undefined; } WritableStreamDealWithRejection(stream, error); } -// TODO(ricea): Fix alphabetical order. -function WritableStreamCloseQueuedOrInFlight(stream) { - if (stream._closeRequest === undefined && stream._inFlightCloseRequest === undefined) { - return false; - } +function WritableStreamFinishInFlightWrite(stream) { + assert(stream._inFlightWriteRequest !== undefined); + resolvePromise(stream._inFlightWriteRequest, undefined); + stream._inFlightWriteRequest = undefined; +} - return true; +function WritableStreamFinishInFlightWriteWithError(stream, error) { + assert(stream._inFlightWriteRequest !== undefined); + rejectPromise(stream._inFlightWriteRequest, error); + stream._inFlightWriteRequest = undefined; + + assert(stream._state === 'writable' || stream._state === 'erroring'); + + WritableStreamDealWithRejection(stream, error); } function WritableStreamHasOperationMarkedInFlight(stream) { @@ -427,16 +349,36 @@ function WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream) { if (stream._closeRequest !== undefined) { assert(stream._inFlightCloseRequest === undefined); - stream._closeRequest._reject(stream._storedError); + rejectPromise(stream._closeRequest, stream._storedError); stream._closeRequest = undefined; } const writer = stream._writer; if (writer !== undefined) { - defaultWriterClosedPromiseReject(writer, stream._storedError); + rejectPromise(writer._closedPromise, stream._storedError); setPromiseIsHandledToTrue(writer._closedPromise); } } +function WritableStreamStartErroring(stream, reason) { + verbose('WritableStreamStartErroring(stream, %o)', reason); + assert(stream._storedError === undefined); + assert(stream._state === 'writable'); + + const controller = stream._writableStreamController; + assert(controller !== undefined); + + stream._state = 'erroring'; + stream._storedError = reason; + const writer = stream._writer; + if (writer !== undefined) { + WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason); + } + + if (WritableStreamHasOperationMarkedInFlight(stream) === false && controller._started === true) { + WritableStreamFinishErroring(stream); + } +} + function WritableStreamUpdateBackpressure(stream, backpressure) { assert(stream._state === 'writable'); assert(WritableStreamCloseQueuedOrInFlight(stream) === false); @@ -444,159 +386,19 @@ function WritableStreamUpdateBackpressure(stream, backpressure) { const writer = stream._writer; if (writer !== undefined && backpressure !== stream._backpressure) { if (backpressure === true) { - defaultWriterReadyPromiseReset(writer); + writer._readyPromise = newPromise(); } else { assert(backpressure === false); - defaultWriterReadyPromiseResolve(writer); + resolvePromise(writer._readyPromise, undefined); } } stream._backpressure = backpressure; } -class WritableStreamDefaultWriter { - constructor(stream) { - if (IsWritableStream(stream) === false) { - throw new TypeError('WritableStreamDefaultWriter can only be constructed with a WritableStream instance'); - } - if (IsWritableStreamLocked(stream) === true) { - throw new TypeError('This stream has already been locked for exclusive writing by another writer'); - } - - this._ownerWritableStream = stream; - stream._writer = this; - - const state = stream._state; - - if (state === 'writable') { - if (WritableStreamCloseQueuedOrInFlight(stream) === false && stream._backpressure === true) { - defaultWriterReadyPromiseInitialize(this); - } else { - defaultWriterReadyPromiseInitializeAsResolved(this); - } - - defaultWriterClosedPromiseInitialize(this); - } else if (state === 'erroring') { - defaultWriterReadyPromiseInitializeAsRejected(this, stream._storedError); - setPromiseIsHandledToTrue(this._readyPromise); - defaultWriterClosedPromiseInitialize(this); - } else if (state === 'closed') { - defaultWriterReadyPromiseInitializeAsResolved(this); - defaultWriterClosedPromiseInitializeAsResolved(this); - } else { - assert(state === 'errored'); - - const storedError = stream._storedError; - defaultWriterReadyPromiseInitializeAsRejected(this, storedError); - setPromiseIsHandledToTrue(this._readyPromise); - defaultWriterClosedPromiseInitializeAsRejected(this, storedError); - setPromiseIsHandledToTrue(this._closedPromise); - } - } - - get closed() { - if (IsWritableStreamDefaultWriter(this) === false) { - return promiseRejectedWith(defaultWriterBrandCheckException('closed')); - } - - return this._closedPromise; - } - - get desiredSize() { - if (IsWritableStreamDefaultWriter(this) === false) { - throw defaultWriterBrandCheckException('desiredSize'); - } - - if (this._ownerWritableStream === undefined) { - throw defaultWriterLockException('desiredSize'); - } - - return WritableStreamDefaultWriterGetDesiredSize(this); - } +// Writers - get ready() { - if (IsWritableStreamDefaultWriter(this) === false) { - return promiseRejectedWith(defaultWriterBrandCheckException('ready')); - } - - return this._readyPromise; - } - - abort(reason) { - if (IsWritableStreamDefaultWriter(this) === false) { - return promiseRejectedWith(defaultWriterBrandCheckException('abort')); - } - - if (this._ownerWritableStream === undefined) { - return promiseRejectedWith(defaultWriterLockException('abort')); - } - - return WritableStreamDefaultWriterAbort(this, reason); - } - - close() { - if (IsWritableStreamDefaultWriter(this) === false) { - return promiseRejectedWith(defaultWriterBrandCheckException('close')); - } - - const stream = this._ownerWritableStream; - - if (stream === undefined) { - return promiseRejectedWith(defaultWriterLockException('close')); - } - - if (WritableStreamCloseQueuedOrInFlight(stream) === true) { - return promiseRejectedWith(new TypeError('Cannot close an already-closing stream')); - } - - return WritableStreamDefaultWriterClose(this); - } - - releaseLock() { - if (IsWritableStreamDefaultWriter(this) === false) { - throw defaultWriterBrandCheckException('releaseLock'); - } - - const stream = this._ownerWritableStream; - - if (stream === undefined) { - return; - } - - assert(stream._writer !== undefined); - - WritableStreamDefaultWriterRelease(this); - } - - write(chunk) { - if (IsWritableStreamDefaultWriter(this) === false) { - return promiseRejectedWith(defaultWriterBrandCheckException('write')); - } - - if (this._ownerWritableStream === undefined) { - return promiseRejectedWith(defaultWriterLockException('write to')); - } - - return WritableStreamDefaultWriterWrite(this, chunk); - } -} - -// Abstract operations for the WritableStreamDefaultWriter. - -function IsWritableStreamDefaultWriter(x) { - if (!typeIsObject(x)) { - return false; - } - - if (!Object.prototype.hasOwnProperty.call(x, '_ownerWritableStream')) { - return false; - } - - return true; -} - -// A client of WritableStreamDefaultWriter may use these functions directly to bypass state check. function WritableStreamDefaultWriterAbort(writer, reason) { const stream = writer._ownerWritableStream; @@ -634,20 +436,20 @@ function WritableStreamDefaultWriterCloseWithErrorPropagation(writer) { } function WritableStreamDefaultWriterEnsureClosedPromiseRejected(writer, error) { - if (writer._closedPromiseState === 'pending') { - defaultWriterClosedPromiseReject(writer, error); + if (stateIsPending(writer._closedPromise)) { + rejectPromise(writer._closedPromise, error); } else { - defaultWriterClosedPromiseResetToRejected(writer, error); + writer._closedPromise = promiseRejectedWith(error); } setPromiseIsHandledToTrue(writer._closedPromise); } function WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, error) { verbose('WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, %o)', error); - if (writer._readyPromiseState === 'pending') { - defaultWriterReadyPromiseReject(writer, error); + if (stateIsPending(writer._readyPromise)) { + rejectPromise(writer._readyPromise, error); } else { - defaultWriterReadyPromiseResetToRejected(writer, error); + writer._readyPromise = promiseRejectedWith(error); } setPromiseIsHandledToTrue(writer._readyPromise); } @@ -695,7 +497,7 @@ function WritableStreamDefaultWriterWrite(writer, chunk) { const chunkSize = WritableStreamDefaultControllerGetChunkSize(controller, chunk); if (stream !== writer._ownerWritableStream) { - return promiseRejectedWith(defaultWriterLockException('write to')); + return promiseRejectedWith(new TypeError('Cannot write to a stream using a released writer')); } const state = stream._state; @@ -718,54 +520,11 @@ function WritableStreamDefaultWriterWrite(writer, chunk) { return promise; } -class WritableStreamDefaultController { - constructor() { - throw new TypeError('WritableStreamDefaultController cannot be constructed explicitly'); - } - - error(e) { - if (IsWritableStreamDefaultController(this) === false) { - throw new TypeError( - 'WritableStreamDefaultController.prototype.error can only be used on a WritableStreamDefaultController'); - } - const state = this._controlledWritableStream._state; - if (state !== 'writable') { - // The stream is closed, errored or will be soon. The sink can't do anything useful if it gets an error here, so - // just treat it as a no-op. - return; - } - - WritableStreamDefaultControllerError(this, e); - } - - [AbortSteps](reason) { - const result = this._abortAlgorithm(reason); - WritableStreamDefaultControllerClearAlgorithms(this); - return result; - } - - [ErrorSteps]() { - ResetQueue(this); - } -} - -// Abstract operations implementing interface required by the WritableStream. - -function IsWritableStreamDefaultController(x) { - if (!typeIsObject(x)) { - return false; - } - - if (!Object.prototype.hasOwnProperty.call(x, '_controlledWritableStream')) { - return false; - } - - return true; -} +// Default controllers function SetUpWritableStreamDefaultController(stream, controller, startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, highWaterMark, sizeAlgorithm) { - assert(IsWritableStream(stream) === true); + assert(WritableStream.isImpl(stream)); assert(stream._writableStreamController === undefined); controller._controlledWritableStream = stream; @@ -805,70 +564,35 @@ function SetUpWritableStreamDefaultController(stream, controller, startAlgorithm ); } -function SetUpWritableStreamDefaultControllerFromUnderlyingSink(stream, underlyingSink, highWaterMark, sizeAlgorithm) { +function SetUpWritableStreamDefaultControllerFromUnderlyingSink(stream, underlyingSink, underlyingSinkDict, + highWaterMark, sizeAlgorithm) { assert(underlyingSink !== undefined); - const controller = Object.create(WritableStreamDefaultController.prototype); - - function startAlgorithm() { - return InvokeOrNoop(underlyingSink, 'start', [controller]); - } - - const writeAlgorithm = CreateAlgorithmFromUnderlyingMethod(underlyingSink, 'write', 1, [controller]); - const closeAlgorithm = CreateAlgorithmFromUnderlyingMethod(underlyingSink, 'close', 0, []); - const abortAlgorithm = CreateAlgorithmFromUnderlyingMethod(underlyingSink, 'abort', 1, []); + const controller = WritableStreamDefaultController.new(globalThis); - SetUpWritableStreamDefaultController(stream, controller, startAlgorithm, writeAlgorithm, closeAlgorithm, - abortAlgorithm, highWaterMark, sizeAlgorithm); -} + let startAlgorithm = () => undefined; + let writeAlgorithm = () => promiseResolvedWith(undefined); + let closeAlgorithm = () => promiseResolvedWith(undefined); + let abortAlgorithm = () => promiseResolvedWith(undefined); -// ClearAlgorithms may be called twice. Erroring the same stream in multiple ways will often result in redundant calls. -function WritableStreamDefaultControllerClearAlgorithms(controller) { - controller._writeAlgorithm = undefined; - controller._closeAlgorithm = undefined; - controller._abortAlgorithm = undefined; - controller._strategySizeAlgorithm = undefined; -} - -function WritableStreamDefaultControllerClose(controller) { - EnqueueValueWithSize(controller, 'close', 0); - WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller); -} - -function WritableStreamDefaultControllerGetChunkSize(controller, chunk) { - try { - return controller._strategySizeAlgorithm(chunk); - } catch (chunkSizeE) { - WritableStreamDefaultControllerErrorIfNeeded(controller, chunkSizeE); - return 1; + if ('start' in underlyingSinkDict) { + startAlgorithm = () => underlyingSinkDict.start.call(underlyingSink, controller); } -} - -function WritableStreamDefaultControllerGetDesiredSize(controller) { - return controller._strategyHWM - controller._queueTotalSize; -} - -function WritableStreamDefaultControllerWrite(controller, chunk, chunkSize) { - const writeRecord = { chunk }; - - try { - EnqueueValueWithSize(controller, writeRecord, chunkSize); - } catch (enqueueE) { - WritableStreamDefaultControllerErrorIfNeeded(controller, enqueueE); - return; + if ('write' in underlyingSinkDict) { + writeAlgorithm = chunk => underlyingSinkDict.write.call(underlyingSink, chunk, controller); } - - const stream = controller._controlledWritableStream; - if (WritableStreamCloseQueuedOrInFlight(stream) === false && stream._state === 'writable') { - const backpressure = WritableStreamDefaultControllerGetBackpressure(controller); - WritableStreamUpdateBackpressure(stream, backpressure); + if ('close' in underlyingSinkDict) { + closeAlgorithm = () => underlyingSinkDict.close.call(underlyingSink); + } + if ('abort' in underlyingSinkDict) { + abortAlgorithm = reason => underlyingSinkDict.abort.call(underlyingSink, reason); } - WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller); + SetUpWritableStreamDefaultController( + stream, controller, startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, highWaterMark, sizeAlgorithm + ); } -// Abstract operations for the WritableStreamDefaultController. - function WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller) { verbose('WritableStreamDefaultControllerAdvanceQueueIfNeeded()'); const stream = controller._controlledWritableStream; @@ -900,12 +624,51 @@ function WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller) { } } +function WritableStreamDefaultControllerClearAlgorithms(controller) { + controller._writeAlgorithm = undefined; + controller._closeAlgorithm = undefined; + controller._abortAlgorithm = undefined; + controller._strategySizeAlgorithm = undefined; +} + +function WritableStreamDefaultControllerClose(controller) { + EnqueueValueWithSize(controller, 'close', 0); + WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller); +} + +function WritableStreamDefaultControllerError(controller, error) { + const stream = controller._controlledWritableStream; + + assert(stream._state === 'writable'); + + WritableStreamDefaultControllerClearAlgorithms(controller); + WritableStreamStartErroring(stream, error); +} + function WritableStreamDefaultControllerErrorIfNeeded(controller, error) { if (controller._controlledWritableStream._state === 'writable') { WritableStreamDefaultControllerError(controller, error); } } +function WritableStreamDefaultControllerGetBackpressure(controller) { + const desiredSize = WritableStreamDefaultControllerGetDesiredSize(controller); + return desiredSize <= 0; +} + +function WritableStreamDefaultControllerGetChunkSize(controller, chunk) { + try { + return controller._strategySizeAlgorithm(chunk); + } catch (chunkSizeE) { + WritableStreamDefaultControllerErrorIfNeeded(controller, chunkSizeE); + return 1; + } +} + +function WritableStreamDefaultControllerGetDesiredSize(controller) { + return controller._strategyHWM - controller._queueTotalSize; +} + function WritableStreamDefaultControllerProcessClose(controller) { const stream = controller._controlledWritableStream; @@ -959,156 +722,21 @@ function WritableStreamDefaultControllerProcessWrite(controller, chunk) { ); } -function WritableStreamDefaultControllerGetBackpressure(controller) { - const desiredSize = WritableStreamDefaultControllerGetDesiredSize(controller); - return desiredSize <= 0; -} +function WritableStreamDefaultControllerWrite(controller, chunk, chunkSize) { + const writeRecord = { chunk }; -// A client of WritableStreamDefaultController may use these functions directly to bypass state check. + try { + EnqueueValueWithSize(controller, writeRecord, chunkSize); + } catch (enqueueE) { + WritableStreamDefaultControllerErrorIfNeeded(controller, enqueueE); + return; + } -function WritableStreamDefaultControllerError(controller, error) { const stream = controller._controlledWritableStream; + if (WritableStreamCloseQueuedOrInFlight(stream) === false && stream._state === 'writable') { + const backpressure = WritableStreamDefaultControllerGetBackpressure(controller); + WritableStreamUpdateBackpressure(stream, backpressure); + } - assert(stream._state === 'writable'); - - WritableStreamDefaultControllerClearAlgorithms(controller); - WritableStreamStartErroring(stream, error); -} - -// Helper functions for the WritableStream. - -function streamBrandCheckException(name) { - return new TypeError(`WritableStream.prototype.${name} can only be used on a WritableStream`); -} - -// Helper functions for the WritableStreamDefaultWriter. - -function defaultWriterBrandCheckException(name) { - return new TypeError( - `WritableStreamDefaultWriter.prototype.${name} can only be used on a WritableStreamDefaultWriter`); -} - -function defaultWriterLockException(name) { - return new TypeError('Cannot ' + name + ' a stream using a released writer'); -} - -function defaultWriterClosedPromiseInitialize(writer) { - writer._closedPromise = newPromise((resolve, reject) => { - writer._closedPromise_resolve = resolve; - writer._closedPromise_reject = reject; - writer._closedPromiseState = 'pending'; - }); -} - -function defaultWriterClosedPromiseInitializeAsRejected(writer, reason) { - writer._closedPromise = promiseRejectedWith(reason); - writer._closedPromise_resolve = undefined; - writer._closedPromise_reject = undefined; - writer._closedPromiseState = 'rejected'; -} - -function defaultWriterClosedPromiseInitializeAsResolved(writer) { - writer._closedPromise = promiseResolvedWith(undefined); - writer._closedPromise_resolve = undefined; - writer._closedPromise_reject = undefined; - writer._closedPromiseState = 'resolved'; -} - -function defaultWriterClosedPromiseReject(writer, reason) { - assert(writer._closedPromise_resolve !== undefined); - assert(writer._closedPromise_reject !== undefined); - assert(writer._closedPromiseState === 'pending'); - - writer._closedPromise_reject(reason); - writer._closedPromise_resolve = undefined; - writer._closedPromise_reject = undefined; - writer._closedPromiseState = 'rejected'; -} - -function defaultWriterClosedPromiseResetToRejected(writer, reason) { - assert(writer._closedPromise_resolve === undefined); - assert(writer._closedPromise_reject === undefined); - assert(writer._closedPromiseState !== 'pending'); - - writer._closedPromise = promiseRejectedWith(reason); - writer._closedPromiseState = 'rejected'; -} - -function defaultWriterClosedPromiseResolve(writer) { - assert(writer._closedPromise_resolve !== undefined); - assert(writer._closedPromise_reject !== undefined); - assert(writer._closedPromiseState === 'pending'); - - writer._closedPromise_resolve(undefined); - writer._closedPromise_resolve = undefined; - writer._closedPromise_reject = undefined; - writer._closedPromiseState = 'resolved'; -} - -function defaultWriterReadyPromiseInitialize(writer) { - verbose('defaultWriterReadyPromiseInitialize()'); - writer._readyPromise = newPromise((resolve, reject) => { - writer._readyPromise_resolve = resolve; - writer._readyPromise_reject = reject; - }); - writer._readyPromiseState = 'pending'; -} - -function defaultWriterReadyPromiseInitializeAsRejected(writer, reason) { - verbose('defaultWriterReadyPromiseInitializeAsRejected(writer, %o)', reason); - writer._readyPromise = promiseRejectedWith(reason); - writer._readyPromise_resolve = undefined; - writer._readyPromise_reject = undefined; - writer._readyPromiseState = 'rejected'; -} - -function defaultWriterReadyPromiseInitializeAsResolved(writer) { - verbose('defaultWriterReadyPromiseInitializeAsResolved()'); - writer._readyPromise = promiseResolvedWith(undefined); - writer._readyPromise_resolve = undefined; - writer._readyPromise_reject = undefined; - writer._readyPromiseState = 'fulfilled'; -} - -function defaultWriterReadyPromiseReject(writer, reason) { - verbose('defaultWriterReadyPromiseReject(writer, %o)', reason); - assert(writer._readyPromise_resolve !== undefined); - assert(writer._readyPromise_reject !== undefined); - - writer._readyPromise_reject(reason); - writer._readyPromise_resolve = undefined; - writer._readyPromise_reject = undefined; - writer._readyPromiseState = 'rejected'; -} - -function defaultWriterReadyPromiseReset(writer) { - verbose('defaultWriterReadyPromiseReset()'); - assert(writer._readyPromise_resolve === undefined); - assert(writer._readyPromise_reject === undefined); - - writer._readyPromise = newPromise((resolve, reject) => { - writer._readyPromise_resolve = resolve; - writer._readyPromise_reject = reject; - }); - writer._readyPromiseState = 'pending'; -} - -function defaultWriterReadyPromiseResetToRejected(writer, reason) { - verbose('defaultWriterReadyPromiseResetToRejected(writer, %o)', reason); - assert(writer._readyPromise_resolve === undefined); - assert(writer._readyPromise_reject === undefined); - - writer._readyPromise = promiseRejectedWith(reason); - writer._readyPromiseState = 'rejected'; -} - -function defaultWriterReadyPromiseResolve(writer) { - verbose('defaultWriterReadyPromiseResolve()'); - assert(writer._readyPromise_resolve !== undefined); - assert(writer._readyPromise_reject !== undefined); - - writer._readyPromise_resolve(undefined); - writer._readyPromise_resolve = undefined; - writer._readyPromise_reject = undefined; - writer._readyPromiseState = 'fulfilled'; + WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller); } diff --git a/reference-implementation/lib/byte-length-queuing-strategy.js b/reference-implementation/lib/byte-length-queuing-strategy.js deleted file mode 100644 index 97f312e12..000000000 --- a/reference-implementation/lib/byte-length-queuing-strategy.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; -const { createDataProperty } = require('./helpers.js'); - -module.exports = class ByteLengthQueuingStrategy { - constructor({ highWaterMark }) { - createDataProperty(this, 'highWaterMark', highWaterMark); - } - - size(chunk) { - return chunk.byteLength; - } -}; diff --git a/reference-implementation/lib/count-queuing-strategy.js b/reference-implementation/lib/count-queuing-strategy.js deleted file mode 100644 index 0893a95ae..000000000 --- a/reference-implementation/lib/count-queuing-strategy.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; -const { createDataProperty } = require('./helpers.js'); - -module.exports = class CountQueuingStrategy { - constructor({ highWaterMark }) { - createDataProperty(this, 'highWaterMark', highWaterMark); - } - - size() { - return 1; - } -}; diff --git a/reference-implementation/lib/helpers.js b/reference-implementation/lib/helpers.js deleted file mode 100644 index c853aae65..000000000 --- a/reference-implementation/lib/helpers.js +++ /dev/null @@ -1,277 +0,0 @@ -'use strict'; -const assert = require('assert'); -const { rethrowAssertionErrorRejection } = require('./utils.js'); - -const isFakeDetached = Symbol('is "detached" for our purposes'); - -function IsPropertyKey(argument) { - return typeof argument === 'string' || typeof argument === 'symbol'; -} - -exports.typeIsObject = x => (typeof x === 'object' && x !== null) || typeof x === 'function'; - -exports.createDataProperty = (o, p, v) => { - assert(exports.typeIsObject(o)); - Object.defineProperty(o, p, { value: v, writable: true, enumerable: true, configurable: true }); -}; - -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. - return elements.slice(); -}; - -exports.ArrayBufferCopy = (dest, destOffset, src, srcOffset, n) => { - new Uint8Array(dest).set(new Uint8Array(src, srcOffset, n), destOffset); -}; - -exports.IsFiniteNonNegativeNumber = v => { - if (exports.IsNonNegativeNumber(v) === false) { - return false; - } - - if (v === Infinity) { - return false; - } - - return true; -}; - -exports.IsNonNegativeNumber = v => { - if (typeof v !== 'number') { - return false; - } - - if (Number.isNaN(v)) { - return false; - } - - if (v < 0) { - return false; - } - - return true; -}; - -function 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.Call = Call; - -exports.CreateAlgorithmFromUnderlyingMethod = (underlyingObject, methodName, algoArgCount, extraArgs) => { - assert(underlyingObject !== undefined); - assert(IsPropertyKey(methodName)); - assert(algoArgCount === 0 || algoArgCount === 1); - assert(Array.isArray(extraArgs)); - const method = underlyingObject[methodName]; - if (method !== undefined) { - if (typeof method !== 'function') { - throw new TypeError(`${method} is not a method`); - } - switch (algoArgCount) { - case 0: { - return () => { - return PromiseCall(method, underlyingObject, extraArgs); - }; - } - - case 1: { - return arg => { - const fullArgs = [arg].concat(extraArgs); - return PromiseCall(method, underlyingObject, fullArgs); - }; - } - } - } - return () => promiseResolvedWith(undefined); -}; - -exports.InvokeOrNoop = (O, P, args) => { - assert(O !== undefined); - assert(IsPropertyKey(P)); - assert(Array.isArray(args)); - - const method = O[P]; - if (method === undefined) { - return undefined; - } - - return Call(method, O, args); -}; - -function PromiseCall(F, V, args) { - assert(typeof F === 'function'); - assert(V !== undefined); - assert(Array.isArray(args)); - try { - return promiseResolvedWith(Call(F, V, args)); - } catch (value) { - return promiseRejectedWith(value); - } -} - -exports.PromiseCall = PromiseCall; - -// Not implemented correctly -exports.TransferArrayBuffer = O => { - assert(!exports.IsDetachedBuffer(O)); - const transferredIshVersion = O.slice(); - - // This is specifically to fool tests that test "is transferred" by taking a non-zero-length - // ArrayBuffer and checking if its byteLength starts returning 0. - Object.defineProperty(O, 'byteLength', { - get() { - return 0; - } - }); - O[isFakeDetached] = true; - - return transferredIshVersion; -}; - -// Not implemented correctly -exports.IsDetachedBuffer = O => { - return isFakeDetached in O; -}; - -exports.ValidateAndNormalizeHighWaterMark = highWaterMark => { - highWaterMark = Number(highWaterMark); - if (Number.isNaN(highWaterMark) || highWaterMark < 0) { - throw new RangeError('highWaterMark property of a queuing strategy must be non-negative and non-NaN'); - } - - return highWaterMark; -}; - -exports.MakeSizeAlgorithmFromSizeFunction = size => { - if (size === undefined) { - return () => 1; - } - if (typeof size !== 'function') { - throw new TypeError('size property of a queuing strategy must be a function'); - } - return chunk => size(chunk); -}; - -const originalPromise = Promise; -const originalPromiseThen = Promise.prototype.then; -const originalPromiseResolve = Promise.resolve; -const originalPromiseReject = Promise.reject; - -function newPromise(executor) { - return new originalPromise(executor); -} - -function promiseResolvedWith(value) { - return originalPromiseResolve.call(originalPromise, value); -} - -function promiseRejectedWith(reason) { - return originalPromiseReject.call(originalPromise, reason); -} - -function PerformPromiseThen(promise, onFulfilled, onRejected) { - // There doesn't appear to be any way to correctly emulate the behaviour from JavaScript, so this is just an - // approximation. - return originalPromiseThen.call(promise, onFulfilled, onRejected); -} - -function uponPromise(promise, onFulfilled, onRejected) { - PerformPromiseThen( - PerformPromiseThen(promise, onFulfilled, onRejected), - undefined, - rethrowAssertionErrorRejection - ); -} - -function uponFulfillment(promise, onFulfilled) { - uponPromise(promise, onFulfilled); -} - -function uponRejection(promise, onRejected) { - uponPromise(promise, undefined, onRejected); -} - -function transformPromiseWith(promise, fulfillmentHandler, rejectionHandler) { - return PerformPromiseThen(promise, fulfillmentHandler, rejectionHandler); -} - -function setPromiseIsHandledToTrue(promise) { - PerformPromiseThen(promise, undefined, rethrowAssertionErrorRejection); -} - -exports.newPromise = newPromise; -exports.promiseResolvedWith = promiseResolvedWith; -exports.promiseRejectedWith = promiseRejectedWith; -exports.uponPromise = uponPromise; -exports.uponFulfillment = uponFulfillment; -exports.uponRejection = uponRejection; -exports.transformPromiseWith = transformPromiseWith; -exports.setPromiseIsHandledToTrue = setPromiseIsHandledToTrue; - -exports.WaitForAll = (promises, successSteps, failureSteps) => { - let rejected = false; - const rejectionHandler = arg => { - if (rejected === false) { - rejected = true; - failureSteps(arg); - } - }; - let index = 0; - let fulfilledCount = 0; - const total = promises.length; - const result = new Array(total); - if (total === 0) { - queueMicrotask(() => successSteps(result)); - return; - } - for (const promise of promises) { - const promiseIndex = index; - const fulfillmentHandler = arg => { - result[promiseIndex] = arg; - ++fulfilledCount; - if (fulfilledCount === total) { - successSteps(result); - } - }; - PerformPromiseThen(promise, fulfillmentHandler, rejectionHandler); - ++index; - } -}; - -exports.WaitForAllPromise = (promises, successSteps, failureSteps = undefined) => { - let resolvePromise; - let rejectPromise; - const promise = newPromise((resolve, reject) => { - resolvePromise = resolve; - rejectPromise = reject; - }); - if (failureSteps === undefined) { - failureSteps = arg => { - throw arg; - }; - } - const successStepsWrapper = results => { - try { - const stepsResult = successSteps(results); - resolvePromise(stepsResult); - } catch (e) { - rejectPromise(e); - } - }; - const failureStepsWrapper = reason => { - try { - const stepsResult = failureSteps(reason); - resolvePromise(stepsResult); - } catch (e) { - rejectPromise(e); - } - }; - exports.WaitForAll(promises, successStepsWrapper, failureStepsWrapper); - return promise; -}; diff --git a/reference-implementation/lib/utils.js b/reference-implementation/lib/helpers/miscellaneous.js similarity index 84% rename from reference-implementation/lib/utils.js rename to reference-implementation/lib/helpers/miscellaneous.js index d27388396..a042f33cf 100644 --- a/reference-implementation/lib/utils.js +++ b/reference-implementation/lib/helpers/miscellaneous.js @@ -1,6 +1,8 @@ 'use strict'; const assert = require('assert'); +exports.typeIsObject = x => (typeof x === 'object' && x !== null) || typeof x === 'function'; + exports.rethrowAssertionErrorRejection = e => { // Used throughout the reference implementation, as `.catch(rethrowAssertionErrorRejection)`, to ensure any errors // get shown. There are places in the spec where we do promise transformations and purposefully ignore or don't diff --git a/reference-implementation/lib/helpers/webidl.js b/reference-implementation/lib/helpers/webidl.js new file mode 100644 index 000000000..1e376e840 --- /dev/null +++ b/reference-implementation/lib/helpers/webidl.js @@ -0,0 +1,162 @@ +'use strict'; +const { rethrowAssertionErrorRejection } = require('./miscellaneous.js'); + +const originalPromise = Promise; +const originalPromiseThen = Promise.prototype.then; +const originalPromiseReject = Promise.reject; + +const promiseSideTable = new WeakMap(); + +// https://heycam.github.io/webidl/#a-new-promise +function newPromise() { + // The stateIsPending tracking only works if we never resolve the promises with other promises. + // In this spec, that happens to be true for the promises in question; they are always resolved with undefined. + const sideData = { stateIsPending: true }; + const promise = new originalPromise((resolve, reject) => { + sideData.resolve = resolve; + sideData.reject = reject; + }); + + promiseSideTable.set(promise, sideData); + return promise; +} + +// https://heycam.github.io/webidl/#resolve +function resolvePromise(p, value) { + promiseSideTable.get(p).resolve(value); + promiseSideTable.get(p).stateIsPending = false; +} + +// https://heycam.github.io/webidl/#reject +function rejectPromise(p, reason) { + promiseSideTable.get(p).reject(reason); + promiseSideTable.get(p).stateIsPending = false; +} + +// https://heycam.github.io/webidl/#a-promise-resolved-with +function promiseResolvedWith(value) { + // Cannot use original Promise.resolve since that will return value itself sometimes, unlike Web IDL. + const promise = new originalPromise(resolve => resolve(value)); + promiseSideTable.set(promise, { stateIsPending: false }); + return promise; +} + +// https://heycam.github.io/webidl/#a-promise-rejected-with +function promiseRejectedWith(reason) { + const promise = originalPromiseReject.call(originalPromise, reason); + promiseSideTable.set(promise, { stateIsPending: false }); + return promise; +} + +function PerformPromiseThen(promise, onFulfilled, onRejected) { + // There doesn't appear to be any way to correctly emulate the behaviour from JavaScript, so this is just an + // approximation. + return originalPromiseThen.call(promise, onFulfilled, onRejected); +} + +// https://heycam.github.io/webidl/#dfn-perform-steps-once-promise-is-settled +function uponPromise(promise, onFulfilled, onRejected) { + PerformPromiseThen( + PerformPromiseThen(promise, onFulfilled, onRejected), + undefined, + rethrowAssertionErrorRejection + ); +} + +function uponFulfillment(promise, onFulfilled) { + uponPromise(promise, onFulfilled); +} + +function uponRejection(promise, onRejected) { + uponPromise(promise, undefined, onRejected); +} + +function transformPromiseWith(promise, fulfillmentHandler, rejectionHandler) { + return PerformPromiseThen(promise, fulfillmentHandler, rejectionHandler); +} + +function setPromiseIsHandledToTrue(promise) { + PerformPromiseThen(promise, undefined, rethrowAssertionErrorRejection); +} + +function stateIsPending(promise) { + return promiseSideTable.get(promise).stateIsPending; +} + +Object.assign(exports, { + newPromise, + resolvePromise, + rejectPromise, + promiseResolvedWith, + promiseRejectedWith, + uponPromise, + uponFulfillment, + uponRejection, + transformPromiseWith, + setPromiseIsHandledToTrue, + stateIsPending +}); + +// https://heycam.github.io/webidl/#wait-for-all +function waitForAll(promises, successSteps, failureSteps) { + let rejected = false; + const rejectionHandler = arg => { + if (rejected === false) { + rejected = true; + failureSteps(arg); + } + }; + let index = 0; + let fulfilledCount = 0; + const total = promises.length; + const result = new Array(total); + if (total === 0) { + queueMicrotask(() => successSteps(result)); + return; + } + for (const promise of promises) { + const promiseIndex = index; + const fulfillmentHandler = arg => { + result[promiseIndex] = arg; + ++fulfilledCount; + if (fulfilledCount === total) { + successSteps(result); + } + }; + PerformPromiseThen(promise, fulfillmentHandler, rejectionHandler); + ++index; + } +} + +// https://heycam.github.io/webidl/#waiting-for-all-promise +exports.waitForAllPromise = (promises, successSteps, failureSteps = undefined) => { + let resolveP; + let rejectP; + const promise = new Promise((resolve, reject) => { + resolveP = resolve; + rejectP = reject; + }); + if (failureSteps === undefined) { + failureSteps = arg => { + throw arg; + }; + } + const successStepsWrapper = results => { + try { + const stepsResult = successSteps(results); + resolveP(stepsResult); + } catch (e) { + rejectP(e); + } + }; + const failureStepsWrapper = reason => { + try { + const stepsResult = failureSteps(reason); + resolveP(stepsResult); + } catch (e) { + rejectP(e); + } + }; + waitForAll(promises, successStepsWrapper, failureStepsWrapper); + return promise; +}; diff --git a/reference-implementation/lib/index.js b/reference-implementation/lib/index.js index d42621689..ce8a0e54b 100644 --- a/reference-implementation/lib/index.js +++ b/reference-implementation/lib/index.js @@ -2,15 +2,21 @@ // This file is used as the entry point for browserifying the reference implementation to allow it // to run inside the wpt-runner "browser like" context. -const { ReadableStream } = require('./readable-stream.js'); -const { WritableStream } = require('./writable-stream.js'); -const { TransformStream } = require('./transform-stream.js'); -const ByteLengthQueuingStrategy = require('./byte-length-queuing-strategy.js'); -const CountQueuingStrategy = require('./count-queuing-strategy.js'); - -window.ReadableStream = ReadableStream; -window.WritableStream = WritableStream; -window.TransformStream = TransformStream; -window.ByteLengthQueuingStrategy = ByteLengthQueuingStrategy; -window.CountQueuingStrategy = CountQueuingStrategy; window.gc = gc; + +require('../generated/ByteLengthQueuingStrategy.js').install(window, ['Window']); +require('../generated/CountQueuingStrategy.js').install(window, ['Window']); + +require('../generated/ReadableStream.js').install(window, ['Window']); +require('../generated/ReadableStreamDefaultReader.js').install(window, ['Window']); +require('../generated/ReadableStreamBYOBReader.js').install(window, ['Window']); +require('../generated/ReadableStreamDefaultController.js').install(window, ['Window']); +require('../generated/ReadableByteStreamController.js').install(window, ['Window']); +require('../generated/ReadableStreamBYOBRequest.js').install(window, ['Window']); + +require('../generated/WritableStream.js').install(window, ['Window']); +require('../generated/WritableStreamDefaultWriter.js').install(window, ['Window']); +require('../generated/WritableStreamDefaultController.js').install(window, ['Window']); + +require('../generated/TransformStream.js').install(window, ['Window']); +require('../generated/TransformStreamDefaultController.js').install(window, ['Window']); diff --git a/reference-implementation/package.json b/reference-implementation/package.json index 731e48759..563971428 100644 --- a/reference-implementation/package.json +++ b/reference-implementation/package.json @@ -2,22 +2,24 @@ "private": true, "description": "Reference implementation and tests for the WHATWG Streams Standard", "scripts": { + "pretest": "node compile-idl.js", "test": "npm run lint && npm run wpt", - "wpt": "node --expose_gc run-web-platform-tests.js", + "wpt": "npm run pretest && node --expose_gc run-web-platform-tests.js", "sync-wpt": "git submodule update --init", - "lint": "eslint \"**/*.js\"", + "lint": "eslint .", "coverage": "nyc --reporter=lcov npm test && opener coverage/lcov-report/index.html" }, "author": "Domenic Denicola (https://domenic.me/)", "license": "(CC0-1.0 OR MIT)", "devDependencies": { - "browserify": "^16.2.3", - "debug": "^4.1.0", - "eslint": "^5.12.1", + "browserify": "^16.5.1", + "debug": "^4.1.1", + "eslint": "^6.8.0", "minimatch": "^3.0.4", - "nyc": "^13.0.1", + "nyc": "^15.0.1", "opener": "^1.5.1", - "wpt-runner": "^2.7.0" + "webidl2js": "^16.2.0", + "wpt-runner": "^3.0.1" }, "nyc": { "include": [ diff --git a/reference-implementation/run-web-platform-tests.js b/reference-implementation/run-web-platform-tests.js index d85e49b28..c8520118e 100644 --- a/reference-implementation/run-web-platform-tests.js +++ b/reference-implementation/run-web-platform-tests.js @@ -41,8 +41,17 @@ async function main() { window.eval(bundledJS); }, filter(testPath) { - return !workerTestPattern.test(testPath) && // ignore the worker versions - filterGlobs.some(glob => minimatch(testPath, glob)); + // Ignore the worker versions + if (workerTestPattern.test(testPath)) { + return false; + } + + // Ignore for now: wpt-runner doesn't handle the cross-folder dependencies well. + if (testPath === 'idlharness.any.html') { + return false; + } + + return filterGlobs.some(glob => minimatch(testPath, glob)); } }); diff --git a/reference-implementation/web-platform-tests b/reference-implementation/web-platform-tests index 0ba0c4c07..887350c2f 160000 --- a/reference-implementation/web-platform-tests +++ b/reference-implementation/web-platform-tests @@ -1 +1 @@ -Subproject commit 0ba0c4c07c8d2c23efdcc84dfc9043a3fdccbf19 +Subproject commit 887350c2f46def5b01c4dd1f8d2eee35dfb9c5bb