diff --git a/.editorconfig b/.editorconfig index 874a0b40a..f0b203f28 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,18 +4,19 @@ root = true end_of_line = lf insert_final_newline = true charset = utf-8 -indent_size = 2 +indent_size = 1 indent_style = space trim_trailing_whitespace = true - -[*.{js,bs}] -max_line_length = 120 - -[.gitmodules] -indent_style = tab +max_line_length = 100 [Makefile] indent_style = tab [.travis.yml] indent_size = 2 + +[*.js] +max_line_length = 120 + +[.gitmodules] +indent_style = tab 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..03fdd6beb 100644 --- a/.pr-preview.json +++ b/.pr-preview.json @@ -5,11 +5,5 @@ "force": 1, "md-status": "LS-PR", "md-Text-Macro": "PR-NUMBER {{ pull_request.number }}" - }, - "post_processing": { - "name": "emu-algify", - "options": { - "throwingIndicators": true - } } } 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..4443a981f 100644 --- a/Makefile +++ b/Makefile @@ -2,17 +2,14 @@ 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 + curl https://api.csswg.org/bikeshed/ -f -F file=@index.bs > index.html -F md-Text-Macro="COMMIT-SHA LOCAL COPY" 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..5edff586a 100644 --- a/index.bs +++ b/index.bs @@ -7,49 +7,59 @@ 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
+ 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 +68,5532 @@ 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. -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}}. +

Conventions

-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 depends on the Infra Standard. [[!INFRA]] -

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

+This specification uses the [=abstract operation=] concept from [[!ECMASCRIPT]] for its internal +algorithms. This includes treating their return values as [=completion records=], the use of ! and +? prefixes for unwrapping those completion records. -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. +This specification also uses the [=internal slot=] and [=record=] concepts and notation from +ECMAScript. (Although, the internal slots are on Web IDL [=platform objects=] instead of ECMAScript +objects.) -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. +Finally, 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.

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: - -

-    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 teeing. + 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=].
- 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);
-      }
+ 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.
+
- const view = new Uint8Array(buffer, offset, buffer.byteLength - offset); - return reader.read(view).then(newView => { - return readInto(newView.buffer, offset + newView.byteLength); - }); - } - +

The {{ReadableStream}} class

- 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. - +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. -

Class ReadableStream

+

Interface definition

-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 Web IDL definition for the {{ReadableStream}} class is given as follows: -

Class definition

+ +[Exposed=(Window,Worker,Worklet)] +interface ReadableStream { + constructor(object underlyingSource, optional QueuingStrategy strategy = {}); -<em>This section is non-normative.</em> + readonly attribute boolean locked; -If one were to write the {{ReadableStream}} class in something close to the syntax of [[!ECMASCRIPT]], it would look -like + 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(); -<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> = {}) + // TODO: async iterator +}; - get <a href="#rs-locked">locked</a>() +typedef (ReadableStreamDefaultReader or ReadableStreamBYOBReader) ReadableStreamReader; - <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>() +enum ReadableStreamReaderMode { "byob" }; - <a href="#rs-asynciterator">[@@asyncIterator]</a>({ preventCancel } = {}) - } -</code></pre> +dictionary ReadableStreamGetReaderOptions { + ReadableStreamReaderMode mode; +}; + +dictionary ReadableWritablePair { + required ReadableStream readable; + required WritableStream writable; +}; + +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 { + ReadableStreamControllerCallback start; + ReadableStreamControllerCallback pull; + ReadableStreamCancelCallback 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 ReadableStreamControllerCallback = Promise<void> (ReadableStreamController controller); +callback ReadableStreamCancelCallback = 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.

+

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}}([{ {{ReadableStreamGetReaderOptions/mode}}: "{{ReadableStreamReaderMode/byob}}" }]) +
+

Creates a reader of the type specified by the {{ReadableStreamGetReaderOptions/mode}} option + 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. + +

When {{ReadableStreamGetReaderOptions/mode}} is undefined or not provided, the method creates + a [=default reader=] (an instance of {{ReadableStreamDefaultReader}}). The reader provides the + ability to directly read individual [=chunks=] from the stream via the reader's + {{ReadableStreamDefaultReader/read()}} method. + +

When {{ReadableStreamGetReaderOptions/mode}} is "{{ReadableStreamReaderMode/byob}}", the + method creates a [=BYOB reader=] (an instance of {{ReadableStreamBYOBReader}}). This feature only + works on [=readable byte streams=], 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 [=chunks=] from the stream via the reader's {{ReadableStreamBYOBReader/read()}} + method, into developer-supplied buffers, allowing more precise control over allocation. -

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

+
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. -

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.

-
+

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

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 - {{underlying source/start()}} and {{underlying source/pull()}} methods; see below.

+
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. -

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

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

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. 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. If ! IsReadableStream(*this*) is *false*, throw a *TypeError* exception. - 1. Return ! IsReadableStreamLocked(*this*). - - -
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. Return ! [$IsReadableStreamLocked$]([=this=]).
- - 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_). - - -
getIterator({ preventCancel } = {})
+
+ The cancel(|reason|) method steps are: -
- The getIterator method returns an async iterator which can be used to consume the stream. The - {{ReadableStreamAsyncIteratorPrototype/return()}} method of this iterator object will, by default, - cancel the stream; it will also release the reader. + 1. If ! [$IsReadableStreamLocked$]([=this=]) is true, return [=a promise rejected with=] a + {{TypeError}} exception. + 1. Return ! [$ReadableStreamCancel$]([=this=], |reason|).
- - 1. If ! IsReadableStream(*this*) is *false*, throw a *TypeError* exception. - 1. Let _reader_ be ? AcquireReadableStreamDefaultReader(*this*). - 1. Let _iterator_ be ! ObjectCreate(`ReadableStreamAsyncIteratorPrototype`). - 1. Set _iterator_.[[asyncIteratorReader]] to _reader_. - 1. Set _iterator_.[[preventCancel]] to ! ToBoolean(_preventCancel_). - 1. Return _iterator_. - - -
getReader({ mode } = {})
- -
- The getReader method creates a reader of the type specified by the mode option and locks the stream to the new reader. While the stream is locked, no other reader can be - acquired until this one is 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. - - When mode is undefined, the method creates a default reader (an instance of - {{ReadableStreamDefaultReader}}). The reader provides the ability to directly read individual chunks from the - stream via the reader's {{ReadableStreamDefaultReader/read()}} method. - - When mode is "byob", the getReader method creates a BYOB reader (an - instance of {{ReadableStreamBYOBReader}}). This feature only works on readable byte streams, 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 chunks from the stream via the reader's {{ReadableStreamBYOBReader/read()}} - method, into developer-supplied buffers, allowing more precise control over allocation. -
+
+ The getReader(|options|) method steps + are: - - 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. - + 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). -
- 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. +
+ 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=]. -

-    function readAllChunks(readableStream) {
-      const reader = readableStream.getReader();
-      const chunks = [];
+  
+  function readAllChunks(readableStream) {
+    const reader = readableStream.getReader();
+    const chunks = [];
 
-      return pump();
+    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
-  <a lt="cancel a readable stream">canceling</a> the stream.
-</div>
-
-<h5 id="rs-pipe-through" method for="ReadableStream" lt="pipeThrough(transform, options)">pipeThrough({
-<var ignore>writable</var>, <var ignore>readable</var> }, { <var>preventClose</var>, <var>preventAbort</var>,
-<var>preventCancel</var>, <var>signal</var> } = {})</h5>
-
-<div class="note">
-  The <code>pipeThrough</code> method provides a convenient, chainable way of <a>piping</a> this <a>readable stream</a>
-  through a <a>transform stream</a> (or any other <code>{ writable, readable }</code> 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 <a lt="locked to a reader">lock</a> it for the duration of the pipe, preventing any other
-  consumer from acquiring a reader.
-</div>
-
-<emu-alg>
-  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 `<a idl>AbortSignal</a>` 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_.
-</emu-alg>
-
-<div class="example" id="example-pipe-chain">
-  A typical example of constructing <a>pipe chain</a> using {{ReadableStream/pipeThrough(transform, options)}} would
-  look like
-
-  <pre><code class="lang-javascript">
-    httpResponseBody
-      .pipeThrough(decompressorTransform)
-      .pipeThrough(ignoreNonImageFilesTransform)
-      .pipeTo(mediaGallery);
-  </code></pre>
-</div>
-
-<h5 id="rs-pipe-to" method for="ReadableStream" lt="pipeTo(dest, options)">pipeTo(<var>dest</var>,
-{ <var>preventClose</var>, <var>preventAbort</var>, <var>preventCancel</var>, <var>signal</var> } = {})</h5>
-
-<div class="note">
-  The <code>pipeTo</code> method <a lt="piping">pipes</a> this <a>readable stream</a> to a given <a>writable
-  stream</a>. 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 <a lt="locked to a reader">lock</a> 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:
-
-  <ul>
-    <li><p>An error in the source <a>readable stream</a> will <a lt="abort a writable stream">abort</a> the destination
-    <a>writable stream</a>, unless <code>preventAbort</code> is truthy. The returned promise will be rejected with the
-    source's error, or with any error that occurs during aborting the destination.</p></li>
-
-    <li><p>An error in the destination <a>writable stream</a> will <a lt="cancel a readable stream">cancel</a> the
-    source <a>readable stream</a>, unless <code>preventCancel</code> is truthy. The returned promise will be rejected
-    with the destination's error, or with any error that occurs during canceling the source.</p></li>
-
-    <li><p>When the source <a>readable stream</a> closes, the destination <a>writable stream</a> will be closed, unless
-    <code>preventClose</code> 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.</p></li>
-
-    <li><p>If the destination <a>writable stream</a> starts out closed or closing, the source <a>readable stream</a>
-    will be <a lt="cancel a readable stream">canceled</a>, unless <code>preventCancel</code> 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.</p></li>
-  </ul>
-
-  The <code>signal</code> option can be set to an {{AbortSignal}} to allow aborting an ongoing pipe operation via the
-  corresponding {{AbortController}}. In this case, the source <a>readable stream</a> will be <a lt="cancel a readable
-  stream">canceled</a>, and the destination <a>writable stream</a> <a lt="abort a writable stream">aborted</a>, unless
-  the respective options <code>preventCancel</code> or <code>preventAbort</code> are set.
+  }
+  
 
+  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.
+ 
- - 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 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*, 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 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|).
- -
[@@asyncIterator]({ preventCancel } = {})
- -

- The @@asyncIterator method is an alias of {{ReadableStream/getIterator()}}. -

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

ReadableStreamAsyncIteratorPrototype

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

Internal slots

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

next()

- - - 1. If ! IsReadableStreamAsyncIterator(*this*) is *false*, return a promise rejected with a *TypeError* exception. - 1. Let _reader_ be *this*.[[asyncIteratorReader]]. - 1. If _reader_.[[ownerReadableStream]] is *undefined*, return a promise rejected with a *TypeError* exception. - 1. Return the result of 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: - -
-
\[[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.
- -
\[[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.
-
- -(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 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. +
+ 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)); + +
-

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. +

The {{ReadableStreamDefaultReader}} class

- 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 +The {{ReadableStreamDefaultReader}} class represents a [=default reader=] designed to be vended by a {{ReadableStream}} instance. -

Class definition

- -
- -This section is non-normative. - -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 [=this=].\[[ownerReadableStream]] is undefined, 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|). +
- 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 undefined 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. + +

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 undefined 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. - +
+ The desiredSize attribute's getter steps are: -

Properties of the {{ReadableByteStreamController}} prototype

- -
get byobRequest
- -
- 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 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 [=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*). - - -
enqueue(chunk)
+
+ The error(|e|) + method steps are: -
- 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
-

new -ReadableStreamBYOBRequest()

+

Methods and properties

- - 1. Throw a *TypeError* exception. - +
+
view = byobRequest.{{ReadableStreamBYOBRequest/view}} +
+

Returns the view for writing in to. -

Properties of the {{ReadableStreamBYOBRequest}} prototype

+
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=]. -

get view
+

After this method is called, {{ReadableStreamBYOBRequest/view}} will be transferred and no longer modifiable. - - 1. If ! IsReadableStreamBYOBRequest(*this*) is *false*, throw a *TypeError* exception. - 1. Return *this*.[[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=]. -

respond(bytesWritten)
+

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

- - 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_). - +
+ The view + attribute's getter steps are: -
respondWithNewView(view)
+ 1. Return [=this=].\[[view]]. +
- - 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_). - +
+ The respond(|bytesWritten|) method steps are: -

Readable stream BYOB controller abstract operations

+ 1. If [=this=].\[[controller]] is undefined, throw a {{TypeError}} exception. + 1. Perform ? [$ReadableByteStreamControllerRespond$]([=this=].\[[controller]], |bytesWritten|). +
-

IsReadableStreamBYOBRequest ( -x )

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

Abstract operations

-

IsReadableByteStreamController -( x )

+

Working with readable streams

- - 1. If Type(_x_) is not Object, return *false*. - 1. If _x_ does not have an [[controlledReadableByteStream]] internal slot, return *false*. - 1. Return *true*. - +The following abstract operations operate on {{ReadableStream}} instances at a higher level. Some +are even meant to be generally useful by other specifications. -

ReadableByteStreamControllerCallPullIfNeeded ( controller )

+
+ 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: - - 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_. - + 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|. -

Writable streams

+

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. +

-

Using 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: -
- 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. + 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|. -

-    readableStream.pipeTo(writableStream)
-      .then(() => console.log("All data successfully written!"))
-      .catch(e => console.error("Something went wrong!", e));
-  
+

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

-
- 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: +
+ 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. +

-

-    function writeArrayToStream(array, writableStream) {
-      const writer = writableStream.getWriter();
-      array.forEach(chunk => writer.write(chunk).catch(() => {}));
+
+ 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. +

- return writer.close(); - } +
+ InitializeReadableStream(|stream|) performs the following + steps: - writeArrayToStream([1, 2, 3, 4, 5], writableStream) - .then(() => console.log("All done!")) - .catch(e => console.error("Error with the stream: " + e)); -
+ 1. Set |stream|.\[[state]] to "`readable`". + 1. Set |stream|.\[[reader]] and |stream|.\[[storedError]] to undefined. + 1. Set |stream|.\[[disturbed]] to false. +
+ +
+ 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. Assert: |stream| [=implements=] {{ReadableStream}}. + 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. Assert: |stream| [=implements=] {{ReadableStream}}. + 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));
-  
+
+ 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| ». +

- 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. +
+ 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|.

-
- 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. +

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. -


-  async function writeRandomBytesForever(writableStream) {
-    const writer = writableStream.getWriter();
+

Interfacing with controllers

- while (true) { - await writer.ready; +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=]. - const bytes = new Uint8Array(writer.desiredSize); - crypto.getRandomValues(bytes); +Each controller class defines two internal methods, which are called by the {{ReadableStream}} +algorithms: - // Purposefully don't await; awaiting writer.ready is enough. - writer.write(bytes).catch(() => {}); - } - } +
+
\[[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. +
+ +(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. Let |readIntoRequest| be Record {\[[promise]]: |promise|}. + 1. [=list/Append=] |readIntoRequest| to |stream|.\[[reader]].\[[readIntoRequests]]. + 1. Return |promise|. +
- writeRandomBytesForever(myWritableStream).catch(e => console.error("Something broke", e)); -
+
+ ReadableStreamAddReadRequest(|stream|) + performs the following steps: + + 1. Assert: |stream|.\[[reader]] [=implements=] {{ReadableStreamDefaultReader}}. + 1. Assert: |stream|.\[[state]] is "`readable"`. + 1. Let |promise| be [=a new promise=]. + 1. Let |readIntoRequest| be Record {\[[promise]]: |promise|}. + 1. [=list/Append=] |readIntoRequest| to |stream|.\[[reader]].\[[readRequests]]. + 1. Return |promise|. +
- 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. +
+ 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.
-
- 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: +
+ 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 ! IsReadableStreamDefaultReader(|reader|) is true, + 1. [=list/For each=] |readRequest| 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 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. +

-

-  async function writeSuppliedBytesForever(writableStream, getBytes) {
-    const writer = writableStream.getWriter();
+
+ 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. - while (true) { - await writer.ready; + 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 note 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. +
+
- const bytes = getBytes(); - writer.write(bytes).catch(() => {}); - } - } -
+
+ ReadableStreamError(|stream|, + |e|) performs the following steps: + + 1. Assert: |stream|.\[[reader]] [=implements=] {{ReadableStreamDefaultReader}}. + 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|.\[[promise]] 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|.\[[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. +
- 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). +
+ ReadableStreamFulfillReadIntoRequest(|stream|, + |chunk|, |done|) performs the following steps: - 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. + 1. Let |reader| be |stream|.\[[reader]]. + 1. Let |readIntoRequest| be |reader|.\[[readIntoRequests]][0]. + 1. [=list/Remove=] |readIntoRequest| from |reader|.\[[readIntoRequests]]. + 1. [=Resolve=] |readIntoRequest|.\[[promise]] with ! [$ReadableStreamCreateReadResult$](|chunk|, + |done|, |reader|.\[[forAuthorCode]]).
-

Class WritableStream

+
+ ReadableStreamFulfillReadRequest(|stream|, |chunk|, + |done|) performs the following steps: -

Class definition

+ 1. Let |reader| be |stream|.\[[reader]]. + 1. Let |readRequest| be |reader|.\[[readRequests]][0]. + 1. [=list/Remove=] |readRequest| from |reader|.\[[readRequests]]. + 1. [=Resolve=] |readRequest|.\[[promise]] with ! [$ReadableStreamCreateReadResult$](|chunk|, + |done|, |reader|.\[[forAuthorCode]]). +
-
+
+ ReadableStreamGetNumReadIntoRequests(|stream|) + performs the following steps: -This section is non-normative. + 1. Return |stream|.\[[reader]].\[[readIntoRequests]]'s [=list/size=]. +
+ +
+ 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]]. +
+ ReadableStreamDefaultControllerClose(|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.
- - 1. If ! IsWritableStream(*this*) is *false*, throw a *TypeError* exception. - 1. Return ! IsWritableStreamLocked(*this*). - +
+ 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()}}. +

-
abort(reason)
+
+ 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. 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 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|). +
-
- 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. +
+ 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|).
- - 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_). - +

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|). +
-
close()
+
+ 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. -
- 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). +

This is observable using weak + references. See tc39/proposal-weakrefs#31 for more + detail. - 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. + It performs the following steps: + + 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 undefined, return. + 1. Set |controller|.\[[byobRequest]].\[[controller]] to undefined. + 1. Set |controller|.\[[byobRequest]].\[[view]] to undefined. + 1. Set |controller|.\[[byobRequest]] to undefined. +
+ +
+ 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|). +
-Instances of {{WritableStreamDefaultWriter}} are created with the internal slots described in the following table: +
+ 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 undefined. + 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

+ +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(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 { + WritableStreamStartCallback start; + WritableStreamWriteCallback write; + WritableStreamCloseCallback close; + WritableStreamAbortCallback 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 WritableStreamStartCallback = Promise<void> (WritableStreamDefaultController controller); +callback WritableStreamWriteCallback = Promise<void> (WritableStreamDefaultController controller, optional any chunk); +callback WritableStreamCloseCallback = Promise<void> (); +callback WritableStreamAbortCallback = 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. +
close(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. + +

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. +

- If the stream becomes errored or aborted, or the writer's lock is released, the - returned promise will become rejected. +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. Let |underlyingSinkDict| be |underlyingSinkDict|, [=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: + + 1. Return [=this=].\[[readyPromise]]. +
-If one were to write the {{WritableStreamDefaultController}} class in something close to the syntax of [[!ECMASCRIPT]], -it would look like +
+ The abort(|reason|) + method steps are: -

-  class WritableStreamDefaultController {
-    constructor() // always throws
+ 1. If [=this=].\[[ownerWritableStream]] is undefined, return [=a promise rejected with=] a
+    {{TypeError}} exception.
+ 1. Return ! [$WritableStreamDefaultWriterAbort$]([=this=], |reason|).
+
- error(e) - } -
+
+ 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)
\[[abortAlgorithm]] - A promise-returning algorithm, taking one argument (the abort reason), which communicates - a requested abort to the underlying sink -
Internal SlotDescription (non-normative)
\[[closeAlgorithm]] - A promise-returning algorithm which communicates a requested close to the underlying - sink -
\[[abortAlgorithm]] + A promise-returning algorithm, taking one argument (the abort reason), + which communicates a requested abort to the [=underlying sink=]
\[[controlledWritableStream]] - The {{WritableStream}} instance controlled -
\[[closeAlgorithm]] + A promise-returning algorithm which communicates a requested close to + the [=underlying sink=]
\[[queue]] - A List representing the stream's internal queue of chunks -
\[[controlledWritableStream]] + The {{WritableStream}} instance controlled
\[[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 sink has finished starting -
\[[queueTotalSize]] + The total size of all the chunks stored in \[[queue]] (see + [[#queue-with-sizes]])
\[[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 -
\[[started]] + A boolean flag indicating whether the [=underlying sink=] 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 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=]
\[[writeAlgorithm]] - A promise-returning algorithm, taking one argument (the chunk to write), which - writes data to the underlying sink -
\[[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=]
-

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 manipualtion 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|). +
+ +
+ 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]]. +
+ +
+ WritableStreamDefaultControllerGetBackpressure(|controller|) + performs the following steps: + + 1. Let |desiredSize| be ! [$WritableStreamDefaultControllerGetDesiredSize$](|controller|). + 1. Return true if |desiredSize| ≤ 0, or false otherwise. +
+ +
+ 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)); + +
-

-    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); +
-
- 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. +
+ 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(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

+ +The {{TransformStream()}} constructor accepts as its first argument a JavaScript object representing +the [=transformer=]. Such objects can contain any of the following methods: + + +dictionary Transformer { + TransformStreamControllerCallback start; + TransformStreamTransformCallback transform; + TransformStreamControllerCallback 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 TransformStreamControllerCallback = Promise<void> (TransformStreamDefaultController controller); +callback TransformStreamTransformCallback = 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. + +

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. 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 |writableSizeAlgorithm| be ? [$ExtractSizeAlgorithm$](|writableStrategy|). + 1. Let |writableHighWaterMark| be ? [$ExtractHighWaterMark$](|writableStrategy|, 1). + 1. Let |readableSizeAlgorithm| be ? [$ExtractSizeAlgorithm$](|readableStrategy|). + 1. Let |readableHighWaterMark| be ? [$ExtractHighWaterMark$](|readableStrategy|, 0). + 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 -
Internal SlotDescription (non-normative)
\[[backpressureChangePromise]] - A promise which is fulfilled and replaced every time the value of \[[backpressure]] - changes -
\[[controlledTransformStream]] + The {{TransformStream}} instance controlled
\[[readable]] - The {{ReadableStream}} instance controlled by this object -
\[[flushAlgorithm]] + A promise-returning algorithm which communicates a requested close to + the [=transformer=]
\[[transformStreamController]] - A {{TransformStreamDefaultController}} created with the ability to control \[[readable]] - and \[[writable]]; also used for the IsTransformStream brand check -
\[[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]]. - +

Default controllers

-
get writable
+The following abstract operations support the implementaiton of the +{{TransformStreamDefaultController}} class. -
- 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

- -
+
+ 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

- -Instances of {{TransformStreamDefaultController}} are created with the internal slots described in the following table: - - - - - - - - - - - - - -
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 -
+
+ 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. -

new TransformStreamDefaultController()

+ It performs the following steps: -
- The TransformStreamDefaultController constructor cannot be used directly; - {{TransformStreamDefaultController}} instances are created automatically during {{TransformStream}} construction. + 1. Perform ! [$TransformStreamError$](|controller|.\[[controlledTransformStream]], |e|).
- - 1. Throw a *TypeError* exception. - - -

Properties of the {{TransformStreamDefaultController}} prototype

+
+ 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|. +
-
get -desiredSize
+
+ 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|). +
-
- 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. +

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|).
- - 1. If ! IsTransformStreamDefaultController(*this*) is *false*, throw a *TypeError* exception. - 1. Let _readableController_ be *this*.[[controlledTransformStream]].[[readable]].[[readableStreamController]]. - 1. Return ! ReadableStreamDefaultControllerGetDesiredSize(_readableController_). - +
+ TransformStreamDefaultSinkAbortAlgorithm(|stream|, + |reason|) performs the following steps: -
enqueue(chunk)
+ 1. Perform ! [$TransformStreamError$](|stream|, |reason|). + 1. Return [=a promise resolved with=] undefined. +
-
- The enqueue method will enqueue a given chunk in the readable side. +
+ 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. Perform ? TransformStreamDefaultControllerEnqueue(*this*, _chunk_). - +

Default source

-
error(reason)
+The following abstract operation is used to implement the [=underlying source=] for the [=readable +side=] of [=transform streams=]. -
- 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. -
+
+ TransformStreamDefaultSourcePullAlgorithm(|stream|) + performs the following steps: - - 1. If ! IsTransformStreamDefaultController(*this*) is *false*, throw a *TypeError* exception. - 1. Perform ! TransformStreamDefaultControllerError(*this*, _reason_). - + 1. Assert: |stream|.\[[backpressure]] is true. + 1. Assert: |stream|.\[[backpressureChangePromise]] is not undefined. + 1. Perform ! [$TransformStreamSetBackpressure$](|stream|, false). + 1. Return |stream|.\[[backpressureChangePromise]]. +
-
terminate()
+

Queuing strategies

-
- 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

+

The queuing strategy API

-
+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: -This section is non-normative. + +dictionary QueuingStrategy { + unrestricted double highWaterMark; + QueuingStrategySize size; +}; -The {{ReadableStream()}}, {{WritableStream()}}, and {{TransformStream()}} constructors all accept at least one argument -representing an appropriate <a>queuing strategy</a> for the stream being created. Such objects contain the following -properties: +callback QueuingStrategySize = unrestricted double (optional any chunk); +
-
size(chunk) (non-byte streams only)
+
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. + +

highWaterMark
-

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 {{underlying source/pull()}} method - is called.

+

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

-

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

+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: -

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

- + +dictionary QueuingStrategyInit { + required unrestricted double highWaterMark; +}; + -
highWaterMark
-
-

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

-
- +

The {{ByteLengthQueuingStrategy}} class

-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}}. +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. +
+ 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=].
-

Class ByteLengthQueuingStrategy

+

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. -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. +

Interface definition

-
- When creating a readable stream or writable stream, you can supply a byte-length queuing strategy - directly: +The Web IDL definition for the {{ByteLengthQueuingStrategy}} class is given as follows: + + +[Exposed=(Window,Worker,Worklet)] +interface ByteLengthQueuingStrategy { + constructor(QueuingStrategyInit init); - <pre><code class="lang-javascript"> - const stream = new ReadableStream( - { ... }, - new ByteLengthQueuingStrategy({ highWaterMark: 16 * 1024 }) - ); - </code></pre> + attribute unrestricted double highWaterMark; + readonly attribute Function size; +}; + - 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. +

Internal slots

-

-    const stream = new WritableStream(
-      { ... },
-      new ByteLengthQueuingStrategy({ highWaterMark: 32 * 1024 })
-    );
-  
+Instances of {{ByteLengthQueuingStrategy}} have a \[[highWaterMark]] internal slot, storing the +value given in the constructor. - 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. +
+ 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: + + 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|. (The [=callback context=] does not matter, since this function is + never [=invoked=].) + +

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.

-

- 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. -

+

Constructor and properties

-
Class definition
+
+
strategy = new {{ByteLengthQueuingStrategy/constructor(init)|ByteLengthQueuingStrategy}}({ {{QueuingStrategyInit/highWaterMark}} }) +
+

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

+

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. -This section is non-normative. +

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

Returns the [=high water mark=] provided to the constructor, or sets it to a new value. -If one were to write the {{ByteLengthQueuingStrategy}} class in something close to the syntax of [[!ECMASCRIPT]], it -would look like +

Note that high water marks are only read from [=queuing strategies=] upon stream construction, + i.e. setting this property will not change the behavior of any already-constructed streams. -


-  class ByteLengthQueuingStrategy {
-    constructor({ highWaterMark })
-    size(chunk)
-  }
-
+
strategy.{{ByteLengthQueuingStrategy/size}}(chunk) +
+

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

-Each {{ByteLengthQueuingStrategy}} instance will additionally have an own data property -highWaterMark set by its constructor. +
+ The ByteLengthQueuingStrategy(|init|) constructor steps are: + 1. Set [=this=].\[[highWaterMark]] to |init|["{{QueuingStrategyInit/highWaterMark}}"].
-
new -ByteLengthQueuingStrategy({ highWaterMark })
+
+ The highWaterMark + attribute's getter steps are: -
- The constructor takes a non-negative number for the high-water mark, and stores it on the object as a property. + 1. Return [=this=].\[[highWaterMark]]. + + Its setter steps are: + + 1. Set [=this=].\[[highWaterMark]] to [=the given value=].
- - 1. Perform ! CreateDataProperty(*this*, `"highWaterMark"`, _highWaterMark_). - +
+ The size attribute's getter + steps are: -
Properties of the {{ByteLengthQueuingStrategy}} prototype
+ 1. Return [=this=]'s [=relevant global object=]'s [=byte length queuing strategy size function=]. +
-
size(chunk)
+

The {{CountQueuingStrategy}} class

-
- The size method returns the given chunk's byteLength property. (If the chunk doesn't have - one, it will return undefined, causing the stream using this strategy to error.) +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. - This method is intentionally generic; it does not require that its this value be a - ByteLengthQueuingStrategy object. +
+ 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. Return ? GetV(_chunk_, `"byteLength"`). - +

Interface definition

-

Class CountQueuingStrategy

+The Web IDL definition for the {{CountQueuingStrategy}} class is given as follows: -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. + +[Exposed=(Window,Worker,Worklet)] +interface CountQueuingStrategy { + constructor(QueuingStrategyInit init); -<div class="example" id="example-cqs"> - When creating a <a>readable stream</a> or <a>writable stream</a>, you can supply a count queuing strategy directly: + attribute unrestricted double highWaterMark; + readonly attribute Function size; +}; + -

-    const stream = new ReadableStream(
-      { ... },
-      new CountQueuingStrategy({ highWaterMark: 10 })
-    );
-  
+

Internal slots

- 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. +Instances of {{CountQueuingStrategy}} have a \[[highWaterMark]] internal slot, storing the +value given in the constructor. -

-    const stream = new WritableStream(
-      { ... },
-      new CountQueuingStrategy({ highWaterMark: 5 })
-    );
-  
+
+ Additionally, every [=/global object=] |globalObject| has an associated count queuing strategy + size function, which is a {{Function}} whose value must be initialized as follows: - 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. 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|. (The [=callback context=] does not matter, since this function is never + [=invoked=].) + +

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.

-
Class definition
+

Constructor and properties

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

Creates a new {{CountQueuingStrategy}} with the provided [=high water mark=]. -This section is non-normative. +

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. -If one were to write the {{CountQueuingStrategy}} class in something close to the syntax of [[!ECMASCRIPT]], it would -look like +

highWaterMark = strategy.{{CountQueuingStrategy/highWaterMark}} +
strategy.{{CountQueuingStrategy/highWaterMark}} = highWaterMark +
+

Returns the [=high water mark=] provided to the constructor, or sets it to a new value. -


-  class CountQueuingStrategy {
-    constructor({ highWaterMark })
-    size(chunk)
-  }
-
+

Note that high water marks are only read from [=queuing strategies=] upon stream construction, + i.e. setting this property will not change the behavior of any already-constructed streams. + +

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. +

-Each {{CountQueuingStrategy}} instance will additionally have an own data property highWaterMark -set by its constructor. +
+ The CountQueuingStrategy(|init|) constructor steps are: + 1. Set [=this=].\[[highWaterMark]] to |init|["{{QueuingStrategyInit/highWaterMark}}"].
-
new -CountQueuingStrategy({ highWaterMark })
+
+ The highWaterMark + attribute's getter steps are: -
- The constructor takes a non-negative number for the high-water mark, and stores it on the object as a property. + 1. Return [=this=].\[[highWaterMark]]. + + Its setter steps are: + + 1. Set [=this=].\[[highWaterMark]] to [=the given value=].
- - 1. Perform ! CreateDataProperty(*this*, `"highWaterMark"`, _highWaterMark_). - +
+ The size attribute's getter + steps are: -
Properties of the {{CountQueuingStrategy}} prototype
+ 1. Return [=this=]'s [=relevant global object=]'s [=count queuing strategy size function=]. +
-
size()
+

Abstract operations

-
- 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 -)

+The following algorithms are used by the stream constructors to extract the relevant pieces from +a {{QueuingStrategy}} dictionary. -
- InvokeOrNoop is a slight modification of the [[!ECMASCRIPT]] Invoke abstract operation to return - undefined when the method is not present. +
+ ExtractHighWaterMark(|strategy|, |defaultHWM|) + performs the following steps: + + 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. +

+ +
+ ExtractSizeAlgorithm(|strategy|) + performs the following steps: + + 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. 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_). - +

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]]. +
-

IsFiniteNonNegativeNumber ( v -)

+
+ EnqueueValueWithSize(|container|, |value|, |size|) performs the + following steps: - - 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. If ! [$IsNonNegativeNumber$](|v|) is false, throw a {{RangeError}} exception. + 1. If |v| is +∞, throw a {{RangeError}} exception. + 1. [=list/Append=] Record {\[[value]]: |value|, \[[size]]: |size|} to |container|.\[[queue]]. + 1. Set |container|.\[[queueTotalSize]] to |container|.\[[queueTotalSize]] + |size|. +
-

IsNonNegativeNumber ( v )

+
+ PeekQueueValue(|container|) + performs the following steps: - - 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*. - + 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]]. +
-

PromiseCall ( F, V, -args )

+
+ ResetQueue(|container|) + 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. Assert: |container| has \[[queue]] and \[[queueTotalSize]] internal slots. + 1. Set |container|.\[[queue]] to a new empty [=list=]. + 1. Set |container|.\[[queueTotalSize]] to 0. +
-
- The {{ReadableStreamDefaultReader}}, {{ReadableStreamBYOBReader}}, {{ReadableStreamDefaultController}}, - {{ReadableByteStreamController}}, {{WritableStreamDefaultWriter}}, {{WritableStreamDefaultController}}, and - {{TransformStreamDefaultController}} classes are specifically not exposed, as they are not independently useful. +

Miscellaneous

+ +The following abstract operations are a grab-bag of utilities. + +
+ IsNonNegativeNumber(|v|) performs the following steps: + + 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. +
+ +
+ 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 +5602,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" - } -}