From fcb4c03b834183b4871db07e9d30f427a6068724 Mon Sep 17 00:00:00 2001 From: Anne van Kesteren Date: Mon, 19 Feb 2024 10:44:25 -0800 Subject: [PATCH] Review Draft Publication: February 2024 --- index.bs | 2 +- review-drafts/2024-02.bs | 8355 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 8356 insertions(+), 1 deletion(-) create mode 100644 review-drafts/2024-02.bs diff --git a/index.bs b/index.bs index bb77b84c..7202f89a 100644 --- a/index.bs +++ b/index.bs @@ -3,7 +3,7 @@ Group: WHATWG H1: Streams Shortname: streams Text Macro: TWITTER streamsstandard -Text Macro: LATESTRD 2023-08 +Text Macro: LATESTRD 2024-02 Abstract: This specification provides APIs for creating, composing, and consuming streams of data Abstract: that map efficiently to low-level I/O primitives. Translation: ja https://triple-underscore.github.io/Streams-ja.html diff --git a/review-drafts/2024-02.bs b/review-drafts/2024-02.bs new file mode 100644 index 00000000..78fbea72 --- /dev/null +++ b/review-drafts/2024-02.bs @@ -0,0 +1,8355 @@ +
+Group: WHATWG
+Status: RD
+Date: 2024-02-19
+H1: Streams
+Shortname: streams
+Text Macro: TWITTER streamsstandard
+Text Macro: LATESTRD 2024-02
+Abstract: This specification provides APIs for creating, composing, and consuming streams of data
+Abstract: that map efficiently to low-level I/O primitives.
+Translation: ja https://triple-underscore.github.io/Streams-ja.html
+!Demos: streams.spec.whatwg.org/demos
+Indent: 1
+Markup Shorthands: markdown yes
+
+ + + +
+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: ArrayBuffer; url: #sec-arraybuffer-objects
+  text: DataView; url: #sec-dataview-objects
+  text: Number; url: #sec-ecmascript-language-types-number-type
+  text: SharedArrayBuffer; url: #sec-sharedarraybuffer-objects
+  text: Uint8Array; url: #sec-typedarray-objects
+  text: %Object.prototype%; url: #sec-properties-of-the-object-prototype-object
+ type: dfn
+  text: abstract operation; url: #sec-algorithm-conventions-abstract-operations
+  text: array; url: #sec-array-objects
+  text: async generator; url: #sec-asyncgenerator-objects
+  text: async iterable; url: #sec-asynciterable-interface
+  text: completion record; url: #sec-completion-record-specification-type
+  text: internal slot; url: #sec-object-internal-methods-and-internal-slots
+  text: iterable; url: #sec-iterable-interface
+  text: realm; url: #sec-code-realms
+  text: the current Realm; url: #current-realm
+  text: the typed array constructors table; url: #table-49
+  text: typed array; url: #sec-typedarray-objects
+  text: Number type; url: #sec-ecmascript-language-types-number-type
+  text: Data Block; url: #sec-data-blocks
+ type: abstract-op
+  text: Call; url: #sec-call
+  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: Construct; url: #sec-construct
+  text: DetachArrayBuffer; url: #sec-detacharraybuffer
+  text: Get; url: #sec-get-o-p
+  text: GetIterator; url: #sec-getiterator
+  text: GetMethod; url: #sec-getmethod
+  text: GetV; url: #sec-getv
+  text: IsDetachedBuffer; url: #sec-isdetachedbuffer
+  text: IsInteger; url: #sec-isinteger
+  text: IteratorComplete; url: #sec-iteratorcomplete
+  text: IteratorNext; url: #sec-iteratornext
+  text: IteratorValue; url: #sec-iteratorvalue
+  text: OrdinaryObjectCreate; url: #sec-ordinaryobjectcreate
+  text: SameValue; url: #sec-samevalue
+  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
+urlPrefix: https://webassembly.github.io/spec/js-api/; spec: WASM-JS-API-1
+ type: interface
+  text: Memory; url: #memory
+ type: attribute
+  text: buffer; for: Memory; url: #dom-memory-buffer
+url: https://wicg.github.io/compression/#compressionstream; spec: COMPRESSION; type: interface; text: CompressionStream
+
+ + + +

Introduction

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

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

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. + +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. + +[=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. + +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. Other specifications might use the {{GenericTransformStream}} mixin to create classes +with the same writable/readable property pair but other custom APIs +layered on top. + +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. + +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. + +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 [=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 {{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 }). +
+ +

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

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

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

Conventions

+ +This specification depends on the Infra Standard. [[!INFRA]] + +This specification uses the [=abstract operation=] concept from the JavaScript specification for its +internal algorithms. This includes treating their return values as [=completion records=], and the +use of ! and ? prefixes for unwrapping those completion records. [[!ECMASCRIPT]] + +This specification also uses the [=internal slot=] concept and notation from the JavaScript +specification. (Although, the internal slots are on Web IDL [=platform objects=] instead of on +JavaScript objects.) + +

The reasons for the usage of these foreign JavaScript specification conventions are +largely historical. We urge you to avoid following our example when writing your own web +specifications. + +In this specification, all numbers are represented as double-precision 64-bit IEEE 754 floating +point values (like the JavaScript [=Number type=] or Web IDL {{unrestricted double}} type), and all +arithmetic operations performed on them must be done in the standard way for such values. This is +particularly important for the data structure described in [[#queue-with-sizes]]. [[!IEEE-754]] + +

Readable streams

+ +

Using readable streams

+ +
+ The simplest way to consume a readable stream is to simply [=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 [=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 [=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); + const buffer = await readInto(startingAB); + console.log("The first 1024 bytes: ", buffer); + + async function readInto(buffer) { + let offset = 0; + + while (offset < buffer.byteLength) { + const { value: view, done } = + await reader.read(new Uint8Array(buffer, offset, buffer.byteLength - offset)); + buffer = view.buffer; + if (done) { + break; + } + offset += view.byteLength; + } + + return buffer; + } + + + 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 view is destructured from the return value of reading 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. + + Note that this example is mostly educational. For practical purposes, the + {{ReadableStreamBYOBReaderReadOptions/min}} option of {{ReadableStreamBYOBReader/read()}} + provides an easier and more direct way to read an exact number of bytes: + + + const reader = readableStream.getReader({ mode: "byob" }); + const { value: view, done } = await reader.read(new Uint8Array(1024), { min: 1024 }); + console.log("The first 1024 bytes: ", view); + +
+ +

The {{ReadableStream}} class

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

Interface definition

+ +The Web IDL definition for the {{ReadableStream}} class is given as follows: + + +[Exposed=*, Transferable] +interface ReadableStream { + constructor(optional object underlyingSource, optional QueuingStrategy strategy = {}); + + static ReadableStream from(any asyncIterable); + + readonly attribute boolean locked; + + Promise<undefined> cancel(optional any reason); + ReadableStreamReader getReader(optional ReadableStreamGetReaderOptions options = {}); + ReadableStream pipeThrough(ReadableWritablePair transform, optional StreamPipeOptions options = {}); + Promise<undefined> pipeTo(WritableStream destination, optional StreamPipeOptions options = {}); + sequence<ReadableStream> tee(); + + async iterable<any>(optional ReadableStreamIteratorOptions options = {}); +}; + +typedef (ReadableStreamDefaultReader or ReadableStreamBYOBReader) ReadableStreamReader; + +enum ReadableStreamReaderMode { "byob" }; + +dictionary ReadableStreamGetReaderOptions { + ReadableStreamReaderMode mode; +}; + +dictionary ReadableStreamIteratorOptions { + boolean preventCancel = false; +}; + +dictionary ReadableWritablePair { + required ReadableStream readable; + required WritableStream writable; +}; + +dictionary StreamPipeOptions { + boolean preventClose = false; + boolean preventAbort = false; + boolean preventCancel = false; + AbortSignal signal; +}; + + +

Internal slots

+ +Instances of {{ReadableStream}} are created with the internal slots described in the following +table: + + + + + + + + + + + +
Internal Slot + Description (non-normative) +
\[[controller]] + A {{ReadableStreamDefaultController}} or + {{ReadableByteStreamController}} created with the ability to control the state and queue of this + stream +
\[[Detached]] + A boolean flag set to true when the stream is transferred +
\[[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 +
\[[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 +
+ +

The underlying source API

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

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

Typically this is used to 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()}} or + {{ReadableStreamGenericReader/cancel()|reader.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. Throwing an exception is treated the same as returning a rejected promise. + +

+

Even if the cancelation process fails, the stream will still close; it will not be put into + an errored state. This is because a failure in the cancelation process doesn't matter to the + consumer's view of the stream, once they've expressed disinterest in it by canceling. The + failure is only communicated to the immediate caller of the corresponding method. + +

This is different from the behavior of the {{UnderlyingSink/close}} and + {{UnderlyingSink/abort}} options of a {{WritableStream}}'s [=underlying sink=], which upon + failure put the corresponding {{WritableStream}} into an errored state. Those correspond to + specific actions the [=producer=] is requesting and, if those actions fail, they indicate + something more persistently wrong. +

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

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

Constructor, methods, and properties

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

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

stream = {{ReadableStream/from(asyncIterable)|ReadableStream.from}}(asyncIterable) +
+

Creates a new {{ReadableStream}} wrapping the provided [=iterable=] or [=async iterable=]. + +

This can be used to adapt various kinds of objects into a [=readable stream=], such as an + [=array=], an [=async generator=], or a Node.js readable stream. + +

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

[=piping|Pipes=] this [=readable stream=] to a given [=writable stream=] |destination|. The + way in which the piping process behaves under various error conditions can be customized with a + number of passed options. It returns a promise that fulfills when the piping process completes + successfully, or rejects if any errors were encountered. + + Piping a stream will [=locked to a reader|lock=] it for the duration of the pipe, preventing any + other consumer from acquiring a reader. + + Errors and closures of the source and destination streams propagate as follows: + + * 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/preventClose}} 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=]. + +

If this stream is a [=readable byte stream=], then each branch will receive its own copy of + each [=chunk=]. If not, then 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 new ReadableStream(|underlyingSource|, |strategy|) constructor steps are: + + 1. If |underlyingSource| is missing, set it to null. + 1. Let |underlyingSourceDict| be |underlyingSource|, [=converted to an IDL value=] of type + {{UnderlyingSource}}. +

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

+ +
+ The static from(|asyncIterable|) method steps + are: + + 1. Return ? [$ReadableStreamFromIterable$](|asyncIterable|). +
+ +
+ The locked getter steps are: + + 1. Return ! [$IsReadableStreamLocked$]([=this=]). +
+ +
+ The cancel(|reason|) method steps are: + + 1. If ! [$IsReadableStreamLocked$]([=this=]) is true, return [=a promise rejected with=] a + {{TypeError}} exception. + 1. Return ! [$ReadableStreamCancel$]([=this=], |reason|). +
+ +
+ The getReader(|options|) method steps + are: + + 1. If |options|["{{ReadableStreamGetReaderOptions/mode}}"] does not [=map/exist=], return ? + [$AcquireReadableStreamDefaultReader$]([=this=]). + 1. Assert: |options|["{{ReadableStreamGetReaderOptions/mode}}"] is + "{{ReadableStreamReaderMode/byob}}". + 1. Return ? [$AcquireReadableStreamBYOBReader$]([=this=]). + +
+ 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 = []; + + return pump(); + + function pump() { + return reader.read().then(({ value, done }) => { + if (done) { + return chunks; + } + + chunks.push(value); + return pump(); + }); + } + } + + + 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. +
+
+ +
+ 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); + +
+
+ +
+ 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|). + +
+ An ongoing [=pipe=] operation can be stopped using an {{AbortSignal}}, as follows: + + + const controller = new AbortController(); + readable.pipeTo(writable, { signal: controller.signal }); + + // ... some time later ... + controller.abort(); + + + (The above omits error handling for the promise returned by {{ReadableStream/pipeTo()}}. + Additionally, the impact of the {{StreamPipeOptions/preventAbort}} and + {{StreamPipeOptions/preventCancel}} options what happens when piping is stopped are worth + considering.) +
+ +
+ The above technique can be used to switch the {{ReadableStream}} being piped, while writing into + the same {{WritableStream}}: + + + const controller = new AbortController(); + const pipePromise = readable1.pipeTo(writable, { preventAbort: true, signal: controller.signal }); + + // ... some time later ... + controller.abort(); + + // Wait for the pipe to complete before starting a new one: + try { + await pipePromise; + } catch (e) { + // Swallow "AbortError" DOMExceptions as expected, but rethrow any unexpected failures. + if (e.name !== "AbortError") { + throw e; + } + } + + // Start the new pipe! + readable2.pipeTo(writable); + +
+
+ +
+ 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)); + +
+
+ +

Asynchronous iteration

+ +
+
for await (const chunk of stream) { ... } +
for await (const chunk of stream.values({ {{ReadableStreamIteratorOptions/preventCancel}}: true })) { ... } +
+

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

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

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

+
+ +
+ The [=asynchronous iterator initialization steps=] for a {{ReadableStream}}, given |stream|, + |iterator|, and |args|, are: + + 1. Let |reader| be ? [$AcquireReadableStreamDefaultReader$](|stream|). + 1. Set |iterator|'s reader to |reader|. + 1. Let |preventCancel| be |args|[0]["{{ReadableStreamIteratorOptions/preventCancel}}"]. + 1. Set |iterator|'s prevent cancel to + |preventCancel|. +
+ +
+ The [=get the next iteration result=] steps for a {{ReadableStream}}, given stream and |iterator|, are: + + 1. Let |reader| be |iterator|'s [=ReadableStream async iterator/reader=]. + 1. Assert: |reader|.[=ReadableStreamGenericReader/[[stream]]=] is not undefined. + 1. Let |promise| be [=a new promise=]. + 1. Let |readRequest| be a new [=read request=] with the following [=struct/items=]: + : [=read request/chunk steps=], given |chunk| + :: + 1. [=Resolve=] |promise| with |chunk|. + : [=read request/close steps=] + :: + 1. Perform ! [$ReadableStreamDefaultReaderRelease$](|reader|). + 1. [=Resolve=] |promise| with [=end of iteration=]. + : [=read request/error steps=], given |e| + :: + 1. Perform ! [$ReadableStreamDefaultReaderRelease$](|reader|). + 1. [=Reject=] |promise| with |e|. + 1. Perform ! [$ReadableStreamDefaultReaderRead$]([=this=], |readRequest|). + 1. Return |promise|. +
+ +
+ The [=asynchronous iterator return=] steps for a {{ReadableStream}}, given stream, |iterator|, and |arg|, are: + + 1. Let |reader| be |iterator|'s [=ReadableStream async iterator/reader=]. + 1. Assert: |reader|.[=ReadableStreamGenericReader/[[stream]]=] is not undefined. + 1. Assert: |reader|.[=ReadableStreamDefaultReader/[[readRequests]]=] is [=list/is empty|empty=], + as the async iterator machinery guarantees that any previous calls to `next()` have settled + before this is called. + 1. If |iterator|'s [=ReadableStream async iterator/prevent cancel=] is false: + 1. Let |result| be ! [$ReadableStreamReaderGenericCancel$](|reader|, |arg|). + 1. Perform ! [$ReadableStreamDefaultReaderRelease$](|reader|). + 1. Return |result|. + 1. Perform ! [$ReadableStreamDefaultReaderRelease$](|reader|). + 1. Return [=a promise resolved with=] undefined. +
+ +

Transfer via `postMessage()`

+ +
+
destination.postMessage(rs, { transfer: [rs] }); +
+

Sends a {{ReadableStream}} to another frame, window, or worker. + +

The transferred stream can be used exactly like the original. The original will become + [=locked to a reader|locked=] and no longer directly usable. +

+
+ +
+ {{ReadableStream}} objects are [=transferable objects=]. Their [=transfer steps=], given |value| + and |dataHolder|, are: + + 1. If ! [$IsReadableStreamLocked$](|value|) is true, throw a "{{DataCloneError}}" {{DOMException}}. + 1. Let |port1| be a [=new=] {{MessagePort}} in [=the current Realm=]. + 1. Let |port2| be a [=new=] {{MessagePort}} in [=the current Realm=]. + 1. [=Entangle=] |port1| and |port2|. + 1. Let |writable| be a [=new=] {{WritableStream}} in [=the current Realm=]. + 1. Perform ! [$SetUpCrossRealmTransformWritable$](|writable|, |port1|). + 1. Let |promise| be ! [$ReadableStreamPipeTo$](|value|, |writable|, false, false, false). + 1. Set |promise|.\[[PromiseIsHandled]] to true. + 1. Set |dataHolder|.\[[port]] to ! [$StructuredSerializeWithTransfer$](|port2|, « |port2| »). +
+ +
+ Their [=transfer-receiving steps=], given |dataHolder| and |value|, are: + + 1. Let |deserializedRecord| be ! [$StructuredDeserializeWithTransfer$](|dataHolder|.\[[port]], + [=the current Realm=]). + 1. Let |port| be |deserializedRecord|.\[[Deserialized]]. + 1. Perform ! [$SetUpCrossRealmTransformReadable$](|value|, |port|). + +
+ +

The {{ReadableStreamGenericReader}} mixin

+ +The {{ReadableStreamGenericReader}} mixin defines common internal slots, getters and methods that +are shared between {{ReadableStreamDefaultReader}} and {{ReadableStreamBYOBReader}} objects. + +

Mixin definition

+ +The Web IDL definition for the {{ReadableStreamGenericReader}} mixin is given as follows: + + +interface mixin ReadableStreamGenericReader { + readonly attribute Promise<undefined> closed; + + Promise<undefined> cancel(optional any reason); +}; + + +

Internal slots

+ +Instances of classes including the {{ReadableStreamGenericReader}} mixin are created with the +internal slots described in the following table: + + + + + + + +
Internal Slot + Description (non-normative) +
\[[closedPromise]] + A promise returned by the reader's + {{ReadableStreamGenericReader/closed}} getter +
\[[stream]] + A {{ReadableStream}} instance that owns this reader +
+ +

Methods and properties

+ +
+ The closed + getter steps are: + + 1. Return [=this=].[=ReadableStreamGenericReader/[[closedPromise]]=]. +
+ +
+ The cancel(|reason|) + method steps are: + + 1. If [=this=].[=ReadableStreamGenericReader/[[stream]]=] is undefined, return [=a promise rejected + with=] a {{TypeError}} exception. + 1. Return ! [$ReadableStreamReaderGenericCancel$]([=this=], |reason|). +
+ +

The {{ReadableStreamDefaultReader}} class

+ +The {{ReadableStreamDefaultReader}} class represents a [=default reader=] designed to be vended by a +{{ReadableStream}} instance. + +

Interface definition

+ +The Web IDL definition for the {{ReadableStreamDefaultReader}} class is given as follows: + + +[Exposed=*] +interface ReadableStreamDefaultReader { + constructor(ReadableStream stream); + + Promise<ReadableStreamReadResult> read(); + undefined releaseLock(); +}; +ReadableStreamDefaultReader includes ReadableStreamGenericReader; + +dictionary ReadableStreamReadResult { + any value; + boolean done; +}; + + +

Internal slots

+ +Instances of {{ReadableStreamDefaultReader}} are created with the internal slots defined by +{{ReadableStreamGenericReader}}, and those described in the following table: + + + + + + +
Internal Slot + Description (non-normative) +
\[[readRequests]] + A [=list=] of [=read requests=], used when a [=consumer=] requests + [=chunks=] sooner than they are available +
+ +A read request is a [=struct=] containing three algorithms to perform in reaction +to filling the [=readable stream=]'s [=internal queue=] or changing its state. It has the following +[=struct/items=]: + +: chunk steps +:: An algorithm taking a [=chunk=], called when a chunk is available for reading +: close steps +:: An algorithm taking no arguments, called when no [=chunks=] are available because the stream is + closed +: error steps +:: An algorithm taking a JavaScript value, called when no [=chunks=] are available because the + stream is errored + +

Constructor, methods, and properties

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

This is equivalent to calling |stream|.{{ReadableStream/getReader()}}. + +

await reader.{{ReadableStreamGenericReader/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.{{ReadableStreamGenericReader/cancel(reason)|cancel}}([ reason ]) +
+

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

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

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

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

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

If the reader's lock is released while it still has pending read requests, then the + promises returned by the reader's {{ReadableStreamDefaultReader/read()}} method are immediately + rejected with a {{TypeError}}. Any unread chunks remain in the stream's [=internal queue=] and can + be read later by acquiring a new reader. +

+ +
+ The new ReadableStreamDefaultReader(|stream|) + constructor steps are: + + 1. Perform ? [$SetUpReadableStreamDefaultReader$]([=this=], |stream|). +
+ +
+ The read() + method steps are: + + 1. If [=this=].[=ReadableStreamGenericReader/[[stream]]=] is undefined, return [=a promise rejected with=] a {{TypeError}} + exception. + 1. Let |promise| be [=a new promise=]. + 1. Let |readRequest| be a new [=read request=] with the following [=struct/items=]: + : [=read request/chunk steps=], given |chunk| + :: + 1. [=Resolve=] |promise| with «[ "{{ReadableStreamReadResult/value}}" → |chunk|, + "{{ReadableStreamReadResult/done}}" → false ]». + : [=read request/close steps=] + :: + 1. [=Resolve=] |promise| with «[ "{{ReadableStreamReadResult/value}}" → undefined, + "{{ReadableStreamReadResult/done}}" → true ]». + : [=read request/error steps=], given |e| + :: + 1. [=Reject=] |promise| with |e|. + 1. Perform ! [$ReadableStreamDefaultReaderRead$]([=this=], |readRequest|). + 1. Return |promise|. +
+ +
+ The releaseLock() method steps are: + + 1. If [=this=].[=ReadableStreamGenericReader/[[stream]]=] is undefined, return. + 1. Perform ! [$ReadableStreamDefaultReaderRelease$]([=this=]). +
+ +

The {{ReadableStreamBYOBReader}} class

+ +The {{ReadableStreamBYOBReader}} class represents a [=BYOB reader=] designed to be vended by a +{{ReadableStream}} instance. + +

Interface definition

+ +The Web IDL definition for the {{ReadableStreamBYOBReader}} class is given as follows: + + +[Exposed=*] +interface ReadableStreamBYOBReader { + constructor(ReadableStream stream); + + Promise<ReadableStreamReadResult> read(ArrayBufferView view, optional ReadableStreamBYOBReaderReadOptions options = {}); + undefined releaseLock(); +}; +ReadableStreamBYOBReader includes ReadableStreamGenericReader; + +dictionary ReadableStreamBYOBReaderReadOptions { + [EnforceRange] unsigned long long min = 1; +}; + + +

Internal slots

+ +Instances of {{ReadableStreamBYOBReader}} are created with the internal slots defined by +{{ReadableStreamGenericReader}}, and those described in the following table: + + + + + + +
Internal Slot + Description (non-normative) +
\[[readIntoRequests]] + A [=list=] of [=read-into requests=], used when a [=consumer=] requests + [=chunks=] sooner than they are available +
+ +A read-into request is a [=struct=] containing three algorithms to perform in +reaction to filling the [=readable byte stream=]'s [=internal queue=] or changing its state. It has +the following [=struct/items=]: + +: chunk steps +:: An algorithm taking a [=chunk=], called when a chunk is available for reading +: close steps +:: An algorithm taking a [=chunk=] or undefined, called when no chunks are available because + the stream is closed +: error steps +:: An algorithm taking a JavaScript value, called when no [=chunks=] are available because the + stream is errored + +

The [=read-into request/close steps=] take a [=chunk=] so that it can return the +backing memory to the caller if possible. For example, +{{ReadableStreamBYOBReader/read()|byobReader.read(chunk)}} will fulfill with { +value: newViewOnSameMemory, done: true } for closed streams. If the stream is +[=cancel a readable stream|canceled=], the backing memory is discarded and +{{ReadableStreamBYOBReader/read()|byobReader.read(chunk)}} fulfills with the more traditional +{ value: undefined, done: true } instead. + +

Constructor, methods, and properties

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

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

await reader.{{ReadableStreamGenericReader/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.{{ReadableStreamGenericReader/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[, { {{ReadableStreamBYOBReaderReadOptions/min}} }]) +
+

Attempts to read 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: newView, done: false }. In this case, |view| will be + [=ArrayBuffer/detached=] and no longer usable, but newView 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: newView, done: true }. In this case, |view| will be + [=ArrayBuffer/detached=] and no longer usable, but newView will be a new view (of + the same type) onto the same backing memory region, with no modifications, to ensure the memory + is returned to the caller. + +
  • If the reader is [=cancel a readable stream|canceled=], the promise will be fulfilled with + an object of the form { value: undefined, done: true }. In this case, + the backing memory region of |view| is discarded and not returned to the caller. + +
  • If the stream becomes errored, the promise will be rejected with the relevant error. +
+ +

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

If {{ReadableStreamBYOBReaderReadOptions/min}} is given, then the promise will only be + fulfilled as soon as the given minimum number of elements are available. Here, the "number of + elements" is given by newView's length (for typed arrays) or + newView's byteLength (for {{DataView}}s). If the stream becomes closed, + then the promise is fulfilled with the remaining elements in the stream, which might be fewer than + the initially requested amount. If not given, then the promise resolves when at least one element + is available. + +

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

If the reader's lock is released while it still has pending read requests, then the + promises returned by the reader's {{ReadableStreamBYOBReader/read()}} method are immediately + rejected with a {{TypeError}}. Any unread chunks remain in the stream's [=internal queue=] and can + be read later by acquiring a new reader. +

+ +
+ The new ReadableStreamBYOBReader(|stream|) constructor + steps are: + + 1. Perform ? [$SetUpReadableStreamBYOBReader$]([=this=], |stream|). +
+ +
+ The read(|view|, |options|) + method steps are: + + 1. If |view|.\[[ByteLength]] is 0, return [=a promise rejected with=] a {{TypeError}} exception. + 1. If |view|.\[[ViewedArrayBuffer]].\[[ArrayBufferByteLength]] is 0, return [=a promise rejected + with=] a {{TypeError}} exception. + 1. If ! [$IsDetachedBuffer$](|view|.\[[ViewedArrayBuffer]]) is true, return + [=a promise rejected with=] a {{TypeError}} exception. + 1. If |options|["{{ReadableStreamBYOBReaderReadOptions/min}}"] is 0, return [=a promise + rejected with=] a {{TypeError}} exception. + 1. If |view| has a \[[TypedArrayName]] internal slot, + 1. If |options|["{{ReadableStreamBYOBReaderReadOptions/min}}"] > |view|.\[[ArrayLength]], + return [=a promise rejected with=] a {{RangeError}} exception. + 1. Otherwise (i.e., it is a {{DataView}}), + 1. If |options|["{{ReadableStreamBYOBReaderReadOptions/min}}"] > |view|.\[[ByteLength]], + return [=a promise rejected with=] a {{RangeError}} exception. + 1. If [=this=].[=ReadableStreamGenericReader/[[stream]]=] is undefined, return [=a promise rejected + with=] a {{TypeError}} exception. + 1. Let |promise| be [=a new promise=]. + 1. Let |readIntoRequest| be a new [=read-into request=] with the following [=struct/items=]: + : [=read-into request/chunk steps=], given |chunk| + :: + 1. [=Resolve=] |promise| with «[ "{{ReadableStreamReadResult/value}}" → |chunk|, + "{{ReadableStreamReadResult/done}}" → false ]». + : [=read-into request/close steps=], given |chunk| + :: + 1. [=Resolve=] |promise| with «[ "{{ReadableStreamReadResult/value}}" → |chunk|, + "{{ReadableStreamReadResult/done}}" → true ]». + : [=read-into request/error steps=], given |e| + :: + 1. [=Reject=] |promise| with |e|. + 1. Perform ! [$ReadableStreamBYOBReaderRead$]([=this=], |view|, |options|["{{ReadableStreamBYOBReaderReadOptions/min}}"], |readIntoRequest|). + 1. Return |promise|. +
+ +
+ The releaseLock() method steps are: + + 1. If [=this=].[=ReadableStreamGenericReader/[[stream]]=] is undefined, return. + 1. Perform ! [$ReadableStreamBYOBReaderRelease$]([=this=]). +
+ +

The {{ReadableStreamDefaultController}} class

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

Interface definition

+ +The Web IDL definition for the {{ReadableStreamDefaultController}} class is given as follows: + + +[Exposed=*] +interface ReadableStreamDefaultController { + readonly attribute unrestricted double? desiredSize; + + undefined close(); + undefined enqueue(optional any chunk); + undefined error(optional any e); +}; + + +

Internal slots

+ +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=] +
\[[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 +
\[[pullAlgorithm]] + A promise-returning algorithm that pulls data from the [=underlying + source=] +
\[[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 +
\[[queue]] + A [=list=] representing the stream's internal queue of [=chunks=] +
\[[queueTotalSize]] + The total size of all the chunks stored in + [=ReadableStreamDefaultController/[[queue]]=] (see [[#queue-with-sizes]]) +
\[[started]] + A boolean flag indicating whether the [=underlying 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 source=] +
\[[strategySizeAlgorithm]] + An algorithm to calculate the size of enqueued [=chunks=], as part of + the stream's [=queuing strategy=] +
\[[stream]] + The {{ReadableStream}} instance controlled +
+ +

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

+ +
+ The desiredSize getter steps are: + + 1. Return ! [$ReadableStreamDefaultControllerGetDesiredSize$]([=this=]). +
+ +
+ The close() method steps are: + + 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$]([=this=]) is false, throw a + {{TypeError}} exception. + 1. Perform ! [$ReadableStreamDefaultControllerClose$]([=this=]). +
+ +
+ The enqueue(|chunk|) method steps are: + + 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$]([=this=]) is false, throw a + {{TypeError}} exception. + 1. Perform ? [$ReadableStreamDefaultControllerEnqueue$]([=this=], |chunk|). +
+ +
+ The error(|e|) method steps are: + + 1. Perform ! [$ReadableStreamDefaultControllerError$]([=this=], |e|). +
+ +

Internal methods

+ +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]]. + +
+ \[[CancelSteps]](|reason|) implements the + [$ReadableStreamController/[[CancelSteps]]$] contract. It performs the following steps: + + 1. Perform ! [$ResetQueue$]([=this=]). + 1. Let |result| be the result of performing + [=this=].[=ReadableStreamDefaultController/[[cancelAlgorithm]]=], passing |reason|. + 1. Perform ! [$ReadableStreamDefaultControllerClearAlgorithms$]([=this=]). + 1. Return |result|. +
+ +
+ \[[PullSteps]](|readRequest|) implements the + [$ReadableStreamController/[[PullSteps]]$] contract. It performs the following steps: + + 1. Let |stream| be [=this=].[=ReadableStreamDefaultController/[[stream]]=]. + 1. If [=this=].[=ReadableStreamDefaultController/[[queue]]=] is not [=list/is empty|empty=], + 1. Let |chunk| be ! [$DequeueValue$]([=this=]). + 1. If [=this=].[=ReadableStreamDefaultController/[[closeRequested]]=] is true and + [=this=].[=ReadableStreamDefaultController/[[queue]]=] [=list/is empty=], + 1. Perform ! [$ReadableStreamDefaultControllerClearAlgorithms$]([=this=]). + 1. Perform ! [$ReadableStreamClose$](|stream|). + 1. Otherwise, perform ! [$ReadableStreamDefaultControllerCallPullIfNeeded$]([=this=]). + 1. Perform |readRequest|'s [=read request/chunk steps=], given |chunk|. + 1. Otherwise, + 1. Perform ! [$ReadableStreamAddReadRequest$](|stream|, |readRequest|). + 1. Perform ! [$ReadableStreamDefaultControllerCallPullIfNeeded$]([=this=]). +
+ +
+ \[[ReleaseSteps]]() + implements the [$ReadableStreamController/[[ReleaseSteps]]$] contract. It performs the following + steps: + + 1. Return. +
+ +

The {{ReadableByteStreamController}} class

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

Interface definition

+ +The Web IDL definition for the {{ReadableByteStreamController}} class is given as follows: + + +[Exposed=*] +interface ReadableByteStreamController { + readonly attribute ReadableStreamBYOBRequest? byobRequest; + readonly attribute unrestricted double? desiredSize; + + undefined close(); + undefined enqueue(ArrayBufferView chunk); + undefined error(optional any e); +}; + + +

Internal slots

+ +Instances of {{ReadableByteStreamController}} are created with the internal slots described in the +following table: + + + + + + + + + + + + + + + + + + + + +
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. +
\[[byobRequest]] + A {{ReadableStreamBYOBRequest}} instance representing the current BYOB + pull request, or null if there are no pending requests +
\[[cancelAlgorithm]] + A promise-returning algorithm, taking one argument (the cancel reason), + which communicates a requested cancelation to the [=underlying 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 +
\[[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 + byte source=] +
\[[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 [=pull-into descriptors=] +
\[[queue]] + A [=list=] of [=readable byte stream queue entry|readable byte stream + queue entries=] representing the stream's internal queue of [=chunks=] +
\[[queueTotalSize]] + The total size, in bytes, of all the chunks stored in + [=ReadableByteStreamController/[[queue]]=] (see [[#queue-with-sizes]]) +
\[[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=] +
\[[stream]] + The {{ReadableStream}} instance controlled +
+ +
+

Although {{ReadableByteStreamController}} instances have + [=ReadableByteStreamController/[[queue]]=] and [=ReadableByteStreamController/[[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. +

+ +A readable byte stream queue entry is a [=struct=] encapsulating the important aspects of +a [=chunk=] for the specific case of [=readable byte streams=]. It has the following +[=struct/items=]: + +: buffer +:: An {{ArrayBuffer}}, which will be a transferred version of + the one originally supplied by the [=underlying byte source=] +: byte offset +:: A nonnegative integer number giving the byte offset derived from the view originally supplied by + the [=underlying byte source=] +: byte length +:: A nonnegative integer number giving the byte length derived from the view originally supplied by + the [=underlying byte source=] + +A pull-into descriptor is a [=struct=] used to represent pending BYOB pull requests. It +has the following [=struct/items=]: + +: buffer +:: An {{ArrayBuffer}} +: buffer byte length +:: A positive integer representing the initial byte length of [=pull-into descriptor/buffer=] +: byte offset +:: A nonnegative integer byte offset into the [=pull-into descriptor/buffer=] where the + [=underlying byte source=] will start writing +: byte length +:: A positive integer number of bytes which can be written into the [=pull-into descriptor/buffer=] +: bytes filled +:: A nonnegative integer number of bytes that have been written into the [=pull-into + descriptor/buffer=] so far +: minimum fill +:: A positive integer representing the minimum number of bytes that must be written into the + [=pull-into descriptor/buffer=] before the associated {{ReadableStreamBYOBReader/read()}} request + may be fulfilled. By default, this equals the [=pull-into descriptor/element size=]. +: element size +:: A positive integer representing the number of bytes that can be written into the [=pull-into + descriptor/buffer=] at a time, using views of the type described by the [=pull-into + descriptor/view constructor=] +: view constructor +:: A [=the typed array constructors table|typed array constructor=] or {{%DataView%}}, which will be + used for constructing a view with which to write into the [=pull-into descriptor/buffer=] +: reader type +:: Either "`default`" or "`byob`", indicating what type of [=readable stream reader=] initiated this + request, or "`none`" if the initiating [=readable stream reader|reader=] was [=release a read + lock|released=] + +

Methods and properties

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

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

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

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

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

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

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

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

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

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

+ +
+ The byobRequest getter steps are: + + 1. Return ! [$ReadableByteStreamControllerGetBYOBRequest$]([=this=]). +
+ +
+ The desiredSize getter steps are: + + 1. Return ! [$ReadableByteStreamControllerGetDesiredSize$]([=this=]). +
+ +
+ The close() method + steps are: + + 1. If [=this=].[=ReadableByteStreamController/[[closeRequested]]=] is true, throw a {{TypeError}} + exception. + 1. If [=this=].[=ReadableByteStreamController/[[stream]]=].[=ReadableStream/[[state]]=] is not + "`readable`", throw a {{TypeError}} exception. + 1. Perform ? [$ReadableByteStreamControllerClose$]([=this=]). +
+ +
+ The enqueue(|chunk|) method steps are: + + 1. If |chunk|.\[[ByteLength]] is 0, throw a {{TypeError}} exception. + 1. If |chunk|.\[[ViewedArrayBuffer]].\[[ArrayBufferByteLength]] is 0, throw a {{TypeError}} + exception. + 1. If [=this=].[=ReadableByteStreamController/[[closeRequested]]=] is true, throw a {{TypeError}} + exception. + 1. If [=this=].[=ReadableByteStreamController/[[stream]]=].[=ReadableStream/[[state]]=] is not + "`readable`", throw a {{TypeError}} exception. + 1. Return ? [$ReadableByteStreamControllerEnqueue$]([=this=], |chunk|). +
+ +
+ The error(|e|) + method steps are: + + 1. Perform ! [$ReadableByteStreamControllerError$]([=this=], |e|). +
+ +

Internal methods

+ +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]]. + +
+ \[[CancelSteps]](|reason|) implements the + [$ReadableStreamController/[[CancelSteps]]$] contract. It performs the following steps: + + 1. Perform ! [$ReadableByteStreamControllerClearPendingPullIntos$]([=this=]). + 1. Perform ! [$ResetQueue$]([=this=]). + 1. Let |result| be the result of performing + [=this=].[=ReadableByteStreamController/[[cancelAlgorithm]]=], passing in |reason|. + 1. Perform ! [$ReadableByteStreamControllerClearAlgorithms$]([=this=]). + 1. Return |result|. +
+ +
+ \[[PullSteps]](|readRequest|) implements the + [$ReadableStreamController/[[PullSteps]]$] contract. It performs the following steps: + + 1. Let |stream| be [=this=].[=ReadableByteStreamController/[[stream]]=]. + 1. Assert: ! [$ReadableStreamHasDefaultReader$](|stream|) is true. + 1. If [=this=].[=ReadableByteStreamController/[[queueTotalSize]]=] > 0, + 1. Assert: ! [$ReadableStreamGetNumReadRequests$](|stream|) is 0. + 1. Perform ! [$ReadableByteStreamControllerFillReadRequestFromQueue$]([=this=], |readRequest|). + 1. Return. + 1. Let |autoAllocateChunkSize| be + [=this=].[=ReadableByteStreamController/[[autoAllocateChunkSize]]=]. + 1. If |autoAllocateChunkSize| is not undefined, + 1. Let |buffer| be [$Construct$]({{%ArrayBuffer%}}, « |autoAllocateChunkSize| »). + 1. If |buffer| is an abrupt completion, + 1. Perform |readRequest|'s [=read request/error steps=], given |buffer|.\[[Value]]. + 1. Return. + 1. Let |pullIntoDescriptor| be a new [=pull-into descriptor=] with +
+
[=pull-into descriptor/buffer=] +
|buffer|.\[[Value]] + +
[=pull-into descriptor/buffer byte length=] +
|autoAllocateChunkSize| + +
[=pull-into descriptor/byte offset=] +
0 + +
[=pull-into descriptor/byte length=] +
|autoAllocateChunkSize| + +
[=pull-into descriptor/bytes filled=] +
0 + +
[=pull-into descriptor/minimum fill=] +
1 + +
[=pull-into descriptor/element size=] +
1 + +
[=pull-into descriptor/view constructor=] +
{{%Uint8Array%}} + +
[=pull-into descriptor/reader type=] +
"`default`" +
+ 1. [=list/Append=] |pullIntoDescriptor| to + [=this=].[=ReadableByteStreamController/[[pendingPullIntos]]=]. + 1. Perform ! [$ReadableStreamAddReadRequest$](|stream|, |readRequest|). + 1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$]([=this=]). +
+ +
+ \[[ReleaseSteps]]() + implements the [$ReadableStreamController/[[ReleaseSteps]]$] contract. It performs the following + steps: + + 1. If [=this=].[=ReadableByteStreamController/[[pendingPullIntos]]=] is not [=list/is empty|empty=], + 1. Let |firstPendingPullInto| be [=this=].[=ReadableByteStreamController/[[pendingPullIntos]]=][0]. + 1. Set |firstPendingPullInto|'s [=pull-into descriptor/reader type=] to "`none`". + 1. Set [=this=].[=ReadableByteStreamController/[[pendingPullIntos]]=] to the [=list=] + « |firstPendingPullInto| ». +
+ +

The {{ReadableStreamBYOBRequest}} class

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

Interface definition

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

Internal slots

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

Methods and properties

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

Returns the view for writing in to, or null if the BYOB request has already been responded to. + +

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

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

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

The new |view| has to be a view onto the same backing memory region as + {{ReadableStreamBYOBRequest/view}}, i.e. its buffer has to equal (or be a + transferred version of) {{ReadableStreamBYOBRequest/view}}'s + buffer. Its byteOffset has to equal {{ReadableStreamBYOBRequest/view}}'s + byteOffset, and its byteLength (representing the number of bytes written) + has to be less than or equal to that of {{ReadableStreamBYOBRequest/view}}. + +

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

+ +
+ The view + getter steps are: + + 1. Return [=this=].[=ReadableStreamBYOBRequest/[[view]]=]. +
+ +
+ The respond(|bytesWritten|) method steps are: + + 1. If [=this=].[=ReadableStreamBYOBRequest/[[controller]]=] is undefined, throw a {{TypeError}} + exception. + 1. If ! [$IsDetachedBuffer$]([=this=].[=ReadableStreamBYOBRequest/[[view]]=].\[[ArrayBuffer]]) + is true, throw a {{TypeError}} exception. + 1. Assert: [=this=].[=ReadableStreamBYOBRequest/[[view]]=].\[[ByteLength]] > 0. + 1. Assert: [=this=].[=ReadableStreamBYOBRequest/[[view]]=].\[[ViewedArrayBuffer]].\[[ByteLength]] + > 0. + 1. Perform ? + [$ReadableByteStreamControllerRespond$]([=this=].[=ReadableStreamBYOBRequest/[[controller]]=], + |bytesWritten|). +
+ +
+ The respondWithNewView(|view|) method steps are: + + 1. If [=this=].[=ReadableStreamBYOBRequest/[[controller]]=] is undefined, throw a {{TypeError}} + exception. + 1. If ! [$IsDetachedBuffer$](|view|.\[[ViewedArrayBuffer]]) is true, + throw a {{TypeError}} exception. + 1. Return ? + [$ReadableByteStreamControllerRespondWithNewView$]([=this=].[=ReadableStreamBYOBRequest/[[controller]]=], + |view|). +
+ +

Abstract operations

+ +

Working with readable streams

+ +The following abstract operations operate on {{ReadableStream}} instances at a higher level. + +
+ AcquireReadableStreamBYOBReader(|stream|) performs + the following steps: + + 1. Let |reader| be a [=new=] {{ReadableStreamBYOBReader}}. + 1. Perform ? [$SetUpReadableStreamBYOBReader$](|reader|, |stream|). + 1. Return |reader|. +
+ +
+ AcquireReadableStreamDefaultReader(|stream|) performs the + following steps: + + 1. Let |reader| be a [=new=] {{ReadableStreamDefaultReader}}. + 1. Perform ? [$SetUpReadableStreamDefaultReader$](|reader|, |stream|). + 1. Return |reader|. +
+ +
+ CreateReadableStream(|startAlgorithm|, |pullAlgorithm|, + |cancelAlgorithm|[, |highWaterMark|, [, |sizeAlgorithm|]]) 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. +

+ +
+ CreateReadableByteStream(|startAlgorithm|, + |pullAlgorithm|, |cancelAlgorithm|) performs the following steps: + + 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|, 0, undefined). + 1. Return |stream|. + +

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

+ +
+ InitializeReadableStream(|stream|) performs the following + steps: + + 1. Set |stream|.[=ReadableStream/[[state]]=] to "`readable`". + 1. Set |stream|.[=ReadableStream/[[reader]]=] and |stream|.[=ReadableStream/[[storedError]]=] to + undefined. + 1. Set |stream|.[=ReadableStream/[[disturbed]]=] to false. +
+ +
+ IsReadableStreamLocked(|stream|) performs the following steps: + + 1. If |stream|.[=ReadableStream/[[reader]]=] is undefined, return false. + 1. Return true. +
+ +
+ + ReadableStreamFromIterable(|asyncIterable|) performs the following steps: + + 1. Let |stream| be undefined. + 1. Let |iteratorRecord| be ? [$GetIterator$](|asyncIterable|, async). + 1. Let |startAlgorithm| be an algorithm that returns undefined. + 1. Let |pullAlgorithm| be the following steps: + 1. Let |nextResult| be [$IteratorNext$](|iteratorRecord|). + 1. If |nextResult| is an abrupt completion, return [=a promise rejected with=] + |nextResult|.\[[Value]]. + 1. Let |nextPromise| be [=a promise resolved with=] |nextResult|.\[[Value]]. + 1. Return the result of [=reacting=] to |nextPromise| with the following fulfillment steps, + given |iterResult|: + 1. If [$Type$](|iterResult|) is not Object, throw a {{TypeError}}. + 1. Let |done| be ? [$IteratorComplete$](|iterResult|). + 1. If |done| is true: + 1. Perform ! [$ReadableStreamDefaultControllerClose$](|stream|.[=ReadableStream/[[controller]]=]). + 1. Otherwise: + 1. Let |value| be ? [$IteratorValue$](|iterResult|). + 1. Perform ! [$ReadableStreamDefaultControllerEnqueue$](|stream|.[=ReadableStream/[[controller]]=], + |value|). + + 1. Let |cancelAlgorithm| be the following steps, given |reason|: + 1. Let |iterator| be |iteratorRecord|.\[[Iterator]]. + 1. Let |returnMethod| be [$GetMethod$](|iterator|, "`return`"). + 1. If |returnMethod| is an abrupt completion, return [=a promise rejected with=] + |returnMethod|.\[[Value]]. + 1. If |returnMethod|.\[[Value]] is undefined, return [=a promise resolved with=] undefined. + 1. Let |returnResult| be [$Call$](|returnMethod|.\[[Value]], |iterator|, « |reason| »). + 1. If |returnResult| is an abrupt completion, return [=a promise rejected with=] + |returnResult|.\[[Value]]. + 1. Let |returnPromise| be [=a promise resolved with=] |returnResult|.\[[Value]]. + 1. Return the result of [=reacting=] to |returnPromise| with the following fulfillment steps, + given |iterResult|: + 1. If [$Type$](|iterResult|) is not Object, throw a {{TypeError}}. + 1. Return undefined. + 1. Set |stream| to ! [$CreateReadableStream$](|startAlgorithm|, |pullAlgorithm|, |cancelAlgorithm|, + 0). + 1. Return |stream|. +
+ +
+ ReadableStreamPipeTo(|source|, |dest|, |preventClose|, |preventAbort|, + |preventCancel|[, |signal|]) 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| was 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|.[=ReadableStream/[[controller]]=] [=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|.[=ReadableStream/[[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 |signal|'s [=AbortSignal/abort reason=]. + 1. Let |actions| be an empty [=ordered set=]. + 1. If |preventAbort| is false, [=set/append=] the following action to |actions|: + 1. If |dest|.[=WritableStream/[[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|.[=ReadableStream/[[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| is [=AbortSignal/aborted=], 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|.[=ReadableStream/[[state]]=] + is or becomes "`errored`", then + 1. If |preventAbort| is false, [=shutdown with an action=] of ! [$WritableStreamAbort$](|dest|, + |source|.[=ReadableStream/[[storedError]]=]) and with + |source|.[=ReadableStream/[[storedError]]=]. + 1. Otherwise, [=shutdown=] with |source|.[=ReadableStream/[[storedError]]=]. + 1. Errors must be propagated backward: if |dest|.[=WritableStream/[[state]]=] + is or becomes "`errored`", then + 1. If |preventCancel| is false, [=shutdown with an action=] of ! + [$ReadableStreamCancel$](|source|, |dest|.[=WritableStream/[[storedError]]=]) and with + |dest|.[=WritableStream/[[storedError]]=]. + 1. Otherwise, [=shutdown=] with |dest|.[=WritableStream/[[storedError]]=]. + 1. Closing must be propagated forward: if |source|.[=ReadableStream/[[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|.[=WritableStream/[[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|.[=WritableStream/[[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|.[=WritableStream/[[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. If |reader| [=implements=] {{ReadableStreamBYOBReader}}, perform + ! [$ReadableStreamBYOBReaderRelease$](|reader|). + 1. Otherwise, perform ! [$ReadableStreamDefaultReaderRelease$](|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|. +

+ +

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

+ ReadableStreamTee(|stream|, + |cloneForBranch2|) will [=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]] + + If |stream| is a [=readable byte stream=], then |cloneForBranch2| is ignored and chunks are cloned + unconditionally. + +

In this standard ReadableStreamTee is always called with |cloneForBranch2| set to + false; other specifications pass true via the [=ReadableStream/tee=] wrapper algorithm. + + It performs the following steps: + + 1. Assert: |stream| [=implements=] {{ReadableStream}}. + 1. Assert: |cloneForBranch2| is a boolean. + 1. If |stream|.[=ReadableStream/[[controller]]=] [=implements=] {{ReadableByteStreamController}}, + return ? [$ReadableByteStreamTee$](|stream|). + 1. Return ? [$ReadableStreamDefaultTee$](|stream|, |cloneForBranch2|). +

+ +
+ ReadableStreamDefaultTee(|stream|, + |cloneForBranch2|) 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 |readAgain| 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, + 1. Set |readAgain| to true. + 1. Return [=a promise resolved with=] undefined. + 1. Set |reading| to true. + 1. Let |readRequest| be a [=read request=] with the following [=struct/items=]: + : [=read request/chunk steps=], given |chunk| + :: + 1. [=Queue a microtask=] to perform the following steps: + 1. Set |readAgain| to false. + 1. Let |chunk1| and |chunk2| be |chunk|. + 1. If |canceled2| is false and |cloneForBranch2| is true, + 1. Let |cloneResult| be [$StructuredClone$](|chunk2|). + 1. If |cloneResult| is an abrupt completion, + 1. Perform ! [$ReadableStreamDefaultControllerError$](|branch1|.[=ReadableStream/[[controller]]=], |cloneResult|.\[[Value]]). + 1. Perform ! [$ReadableStreamDefaultControllerError$](|branch2|.[=ReadableStream/[[controller]]=], |cloneResult|.\[[Value]]). + 1. [=Resolve=] |cancelPromise| with ! [$ReadableStreamCancel$](|stream|, |cloneResult|.\[[Value]]). + 1. Return. + 1. Otherwise, set |chunk2| to |cloneResult|.\[[Value]]. + 1. If |canceled1| is false, perform ! + [$ReadableStreamDefaultControllerEnqueue$](|branch1|.[=ReadableStream/[[controller]]=], + |chunk1|). + 1. If |canceled2| is false, perform ! + [$ReadableStreamDefaultControllerEnqueue$](|branch2|.[=ReadableStream/[[controller]]=], + |chunk2|). + 1. Set |reading| to false. + 1. If |readAgain| is true, perform |pullAlgorithm|. + +

The microtask delay here is necessary because it takes at least a microtask to + detect errors, when we use |reader|.[=ReadableStreamGenericReader/[[closedPromise]]=] below. + We want errors in |stream| to error both branches immediately, so we cannot let successful + synchronously-available reads happen ahead of asynchronously-available errors. + + : [=read request/close steps=] + :: + 1. Set |reading| to false. + 1. If |canceled1| is false, perform ! + [$ReadableStreamDefaultControllerClose$](|branch1|.[=ReadableStream/[[controller]]=]). + 1. If |canceled2| is false, perform ! + [$ReadableStreamDefaultControllerClose$](|branch2|.[=ReadableStream/[[controller]]=]). + 1. If |canceled1| is false or |canceled2| is false, [=resolve=] |cancelPromise| with undefined. + + : [=read request/error steps=] + :: + 1. Set |reading| to false. + 1. Perform ! [$ReadableStreamDefaultReaderRead$](|reader|, |readRequest|). + 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|.[=ReadableStreamGenericReader/[[closedPromise]]=] with reason + |r|, + 1. Perform ! [$ReadableStreamDefaultControllerError$](|branch1|.[=ReadableStream/[[controller]]=], + |r|). + 1. Perform ! [$ReadableStreamDefaultControllerError$](|branch2|.[=ReadableStream/[[controller]]=], + |r|). + 1. If |canceled1| is false or |canceled2| is false, [=resolve=] |cancelPromise| with undefined. + 1. Return « |branch1|, |branch2| ». +

+ +
+ ReadableByteStreamTee(|stream|) + performs the following steps: + + 1. Assert: |stream| [=implements=] {{ReadableStream}}. + 1. Assert: |stream|.[=ReadableStream/[[controller]]=] [=implements=] + {{ReadableByteStreamController}}. + 1. Let |reader| be ? [$AcquireReadableStreamDefaultReader$](|stream|). + 1. Let |reading| be false. + 1. Let |readAgainForBranch1| be false. + 1. Let |readAgainForBranch2| 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 |forwardReaderError| be the following steps, taking a |thisReader| argument: + 1. [=Upon rejection=] of |thisReader|.[=ReadableStreamGenericReader/[[closedPromise]]=] with reason + |r|, + 1. If |thisReader| is not |reader|, return. + 1. Perform ! [$ReadableByteStreamControllerError$](|branch1|.[=ReadableStream/[[controller]]=], + |r|). + 1. Perform ! [$ReadableByteStreamControllerError$](|branch2|.[=ReadableStream/[[controller]]=], + |r|). + 1. If |canceled1| is false or |canceled2| is false, [=resolve=] |cancelPromise| with undefined. + 1. Let |pullWithDefaultReader| be the following steps: + 1. If |reader| [=implements=] {{ReadableStreamBYOBReader}}, + 1. Assert: |reader|.[=ReadableStreamBYOBReader/[[readIntoRequests]]=] is [=list/is empty|empty=]. + 1. Perform ! [$ReadableStreamBYOBReaderRelease$](|reader|). + 1. Set |reader| to ! [$AcquireReadableStreamDefaultReader$](|stream|). + 1. Perform |forwardReaderError|, given |reader|. + 1. Let |readRequest| be a [=read request=] with the following [=struct/items=]: + : [=read request/chunk steps=], given |chunk| + :: + 1. [=Queue a microtask=] to perform the following steps: + 1. Set |readAgainForBranch1| to false. + 1. Set |readAgainForBranch2| to false. + 1. Let |chunk1| and |chunk2| be |chunk|. + 1. If |canceled1| is false and |canceled2| is false, + 1. Let |cloneResult| be [$CloneAsUint8Array$](|chunk|). + 1. If |cloneResult| is an abrupt completion, + 1. Perform ! [$ReadableByteStreamControllerError$](|branch1|.[=ReadableStream/[[controller]]=], |cloneResult|.\[[Value]]). + 1. Perform ! [$ReadableByteStreamControllerError$](|branch2|.[=ReadableStream/[[controller]]=], |cloneResult|.\[[Value]]). + 1. [=Resolve=] |cancelPromise| with ! [$ReadableStreamCancel$](|stream|, |cloneResult|.\[[Value]]). + 1. Return. + 1. Otherwise, set |chunk2| to |cloneResult|.\[[Value]]. + 1. If |canceled1| is false, perform ! + [$ReadableByteStreamControllerEnqueue$](|branch1|.[=ReadableStream/[[controller]]=], + |chunk1|). + 1. If |canceled2| is false, perform ! + [$ReadableByteStreamControllerEnqueue$](|branch2|.[=ReadableStream/[[controller]]=], + |chunk2|). + 1. Set |reading| to false. + 1. If |readAgainForBranch1| is true, perform |pull1Algorithm|. + 1. Otherwise, if |readAgainForBranch2| is true, perform |pull2Algorithm|. + +

The microtask delay here is necessary because it takes at least a microtask to + detect errors, when we use |reader|.[=ReadableStreamGenericReader/[[closedPromise]]=] below. + We want errors in |stream| to error both branches immediately, so we cannot let successful + synchronously-available reads happen ahead of asynchronously-available errors. + + : [=read request/close steps=] + :: + 1. Set |reading| to false. + 1. If |canceled1| is false, perform ! + [$ReadableByteStreamControllerClose$](|branch1|.[=ReadableStream/[[controller]]=]). + 1. If |canceled2| is false, perform ! + [$ReadableByteStreamControllerClose$](|branch2|.[=ReadableStream/[[controller]]=]). + 1. If |branch1|.[=ReadableStream/[[controller]]=].[=ReadableByteStreamController/[[pendingPullIntos]]=] + is not [=list/is empty|empty=], perform ! + [$ReadableByteStreamControllerRespond$](|branch1|.[=ReadableStream/[[controller]]=], 0). + 1. If |branch2|.[=ReadableStream/[[controller]]=].[=ReadableByteStreamController/[[pendingPullIntos]]=] + is not [=list/is empty|empty=], perform ! + [$ReadableByteStreamControllerRespond$](|branch2|.[=ReadableStream/[[controller]]=], 0). + 1. If |canceled1| is false or |canceled2| is false, [=resolve=] |cancelPromise| with undefined. + + : [=read request/error steps=] + :: + 1. Set |reading| to false. + 1. Perform ! [$ReadableStreamDefaultReaderRead$](|reader|, |readRequest|). + 1. Let |pullWithBYOBReader| be the following steps, given |view| and |forBranch2|: + 1. If |reader| [=implements=] {{ReadableStreamDefaultReader}}, + 1. Assert: |reader|.[=ReadableStreamDefaultReader/[[readRequests]]=] is [=list/is empty|empty=]. + 1. Perform ! [$ReadableStreamDefaultReaderRelease$](|reader|). + 1. Set |reader| to ! [$AcquireReadableStreamBYOBReader$](|stream|). + 1. Perform |forwardReaderError|, given |reader|. + 1. Let |byobBranch| be |branch2| if |forBranch2| is true, and |branch1| otherwise. + 1. Let |otherBranch| be |branch2| if |forBranch2| is false, and |branch1| otherwise. + 1. Let |readIntoRequest| be a [=read-into request=] with the following [=struct/items=]: + : [=read-into request/chunk steps=], given |chunk| + :: + 1. [=Queue a microtask=] to perform the following steps: + 1. Set |readAgainForBranch1| to false. + 1. Set |readAgainForBranch2| to false. + 1. Let |byobCanceled| be |canceled2| if |forBranch2| is true, and |canceled1| otherwise. + 1. Let |otherCanceled| be |canceled2| if |forBranch2| is false, and |canceled1| otherwise. + 1. If |otherCanceled| is false, + 1. Let |cloneResult| be [$CloneAsUint8Array$](|chunk|). + 1. If |cloneResult| is an abrupt completion, + 1. Perform ! [$ReadableByteStreamControllerError$](|byobBranch|.[=ReadableStream/[[controller]]=], |cloneResult|.\[[Value]]). + 1. Perform ! [$ReadableByteStreamControllerError$](|otherBranch|.[=ReadableStream/[[controller]]=], |cloneResult|.\[[Value]]). + 1. [=Resolve=] |cancelPromise| with ! [$ReadableStreamCancel$](|stream|, |cloneResult|.\[[Value]]). + 1. Return. + 1. Otherwise, let |clonedChunk| be |cloneResult|.\[[Value]]. + 1. If |byobCanceled| is false, perform ! + [$ReadableByteStreamControllerRespondWithNewView$](|byobBranch|.[=ReadableStream/[[controller]]=], + |chunk|). + 1. Perform ! [$ReadableByteStreamControllerEnqueue$](|otherBranch|.[=ReadableStream/[[controller]]=], + |clonedChunk|). + 1. Otherwise, if |byobCanceled| is false, perform ! + [$ReadableByteStreamControllerRespondWithNewView$](|byobBranch|.[=ReadableStream/[[controller]]=], + |chunk|). + 1. Set |reading| to false. + 1. If |readAgainForBranch1| is true, perform |pull1Algorithm|. + 1. Otherwise, if |readAgainForBranch2| is true, perform |pull2Algorithm|. + +

The microtask delay here is necessary because it takes at least a microtask to + detect errors, when we use |reader|.[=ReadableStreamGenericReader/[[closedPromise]]=] below. + We want errors in |stream| to error both branches immediately, so we cannot let successful + synchronously-available reads happen ahead of asynchronously-available errors. + + : [=read-into request/close steps=], given |chunk| + :: + 1. Set |reading| to false. + 1. Let |byobCanceled| be |canceled2| if |forBranch2| is true, and |canceled1| otherwise. + 1. Let |otherCanceled| be |canceled2| if |forBranch2| is false, and |canceled1| otherwise. + 1. If |byobCanceled| is false, perform ! + [$ReadableByteStreamControllerClose$](|byobBranch|.[=ReadableStream/[[controller]]=]). + 1. If |otherCanceled| is false, perform ! + [$ReadableByteStreamControllerClose$](|otherBranch|.[=ReadableStream/[[controller]]=]). + 1. If |chunk| is not undefined, + 1. Assert: |chunk|.\[[ByteLength]] is 0. + 1. If |byobCanceled| is false, perform ! + [$ReadableByteStreamControllerRespondWithNewView$](|byobBranch|.[=ReadableStream/[[controller]]=], + |chunk|). + 1. If |otherCanceled| is false and + |otherBranch|.[=ReadableStream/[[controller]]=].[=ReadableByteStreamController/[[pendingPullIntos]]=] + is not [=list/is empty|empty=], perform ! + [$ReadableByteStreamControllerRespond$](|otherBranch|.[=ReadableStream/[[controller]]=], 0). + 1. If |byobCanceled| is false or |otherCanceled| is false, [=resolve=] |cancelPromise| with undefined. + + : [=read-into request/error steps=] + :: + 1. Set |reading| to false. + 1. Perform ! [$ReadableStreamBYOBReaderRead$](|reader|, |view|, 1, |readIntoRequest|). + 1. Let |pull1Algorithm| be the following steps: + 1. If |reading| is true, + 1. Set |readAgainForBranch1| to true. + 1. Return [=a promise resolved with=] undefined. + 1. Set |reading| to true. + 1. Let |byobRequest| be ! [$ReadableByteStreamControllerGetBYOBRequest$](|branch1|.[=ReadableStream/[[controller]]=]). + 1. If |byobRequest| is null, perform |pullWithDefaultReader|. + 1. Otherwise, perform |pullWithBYOBReader|, given |byobRequest|.[=ReadableStreamBYOBRequest/[[view]]=] and false. + 1. Return [=a promise resolved with=] undefined. + 1. Let |pull2Algorithm| be the following steps: + 1. If |reading| is true, + 1. Set |readAgainForBranch2| to true. + 1. Return [=a promise resolved with=] undefined. + 1. Set |reading| to true. + 1. Let |byobRequest| be ! [$ReadableByteStreamControllerGetBYOBRequest$](|branch2|.[=ReadableStream/[[controller]]=]). + 1. If |byobRequest| is null, perform |pullWithDefaultReader|. + 1. Otherwise, perform |pullWithBYOBReader|, given |byobRequest|.[=ReadableStreamBYOBRequest/[[view]]=] and 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 ! [$CreateReadableByteStream$](|startAlgorithm|, |pull1Algorithm|, + |cancel1Algorithm|). + 1. Set |branch2| to ! [$CreateReadableByteStream$](|startAlgorithm|, |pull2Algorithm|, + |cancel2Algorithm|). + 1. Perform |forwardReaderError|, given |reader|. + 1. Return « |branch1|, |branch2| ». +

+ +

Interfacing with 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 three internal methods, which are called by the {{ReadableStream}} +algorithms: + +
+
\[[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]](readRequest) +
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. + +
\[[ReleaseSteps]]() +
The controller's steps that run when a [=readable stream reader|reader=] is + [=release a read lock|released=], used to clean up reader-specific resources stored in the controller. +
+ +(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|, + |readRequest|) performs the following steps: + + 1. Assert: |stream|.[=ReadableStream/[[reader]]=] [=implements=] {{ReadableStreamBYOBReader}}. + 1. Assert: |stream|.[=ReadableStream/[[state]]=] is "`readable`" or "`closed`". + 1. [=list/Append=] |readRequest| to + |stream|.[=ReadableStream/[[reader]]=].[=ReadableStreamBYOBReader/[[readIntoRequests]]=]. +
+ +
+ ReadableStreamAddReadRequest(|stream|, |readRequest| + performs the following steps: + + 1. Assert: |stream|.[=ReadableStream/[[reader]]=] [=implements=] {{ReadableStreamDefaultReader}}. + 1. Assert: |stream|.[=ReadableStream/[[state]]=] is "`readable`". + 1. [=list/Append=] |readRequest| to + |stream|.[=ReadableStream/[[reader]]=].[=ReadableStreamDefaultReader/[[readRequests]]=]. +
+ +
+ ReadableStreamCancel(|stream|, |reason|) performs the following + steps: + + 1. Set |stream|.[=ReadableStream/[[disturbed]]=] to true. + 1. If |stream|.[=ReadableStream/[[state]]=] is "`closed`", return [=a promise resolved with=] + undefined. + 1. If |stream|.[=ReadableStream/[[state]]=] is "`errored`", return [=a promise rejected with=] + |stream|.[=ReadableStream/[[storedError]]=]. + 1. Perform ! [$ReadableStreamClose$](|stream|). + 1. Let |reader| be |stream|.[=ReadableStream/[[reader]]=]. + 1. If |reader| is not undefined and |reader| [=implements=] {{ReadableStreamBYOBReader}}, + 1. Let |readIntoRequests| be |reader|.[=ReadableStreamBYOBReader/[[readIntoRequests]]=]. + 1. Set |reader|.[=ReadableStreamBYOBReader/[[readIntoRequests]]=] to an empty [=list=]. + 1. [=list/For each=] |readIntoRequest| of |readIntoRequests|, + 1. Perform |readIntoRequest|'s [=read-into request/close steps=], given undefined. + 1. Let |sourceCancelPromise| be ! + |stream|.[=ReadableStream/[[controller]]=].[$ReadableStreamController/[[CancelSteps]]$](|reason|). + 1. Return the result of [=reacting=] to |sourceCancelPromise| with a fulfillment step that returns + undefined. +
+ +
+ ReadableStreamClose(|stream|) performs the following steps: + + 1. Assert: |stream|.[=ReadableStream/[[state]]=] is "`readable`". + 1. Set |stream|.[=ReadableStream/[[state]]=] to "`closed`". + 1. Let |reader| be |stream|.[=ReadableStream/[[reader]]=]. + 1. If |reader| is undefined, return. + 1. [=Resolve=] |reader|.[=ReadableStreamGenericReader/[[closedPromise]]=] with undefined. + 1. If |reader| [=implements=] {{ReadableStreamDefaultReader}}, + 1. Let |readRequests| be |reader|.[=ReadableStreamDefaultReader/[[readRequests]]=]. + 1. Set |reader|.[=ReadableStreamDefaultReader/[[readRequests]]=] to an empty [=list=]. + 1. [=list/For each=] |readRequest| of |readRequests|, + 1. Perform |readRequest|'s [=read request/close steps=]. +
+ +
+ ReadableStreamError(|stream|, + |e|) performs the following steps: + + 1. Assert: |stream|.[=ReadableStream/[[state]]=] is "`readable`". + 1. Set |stream|.[=ReadableStream/[[state]]=] to "`errored`". + 1. Set |stream|.[=ReadableStream/[[storedError]]=] to |e|. + 1. Let |reader| be |stream|.[=ReadableStream/[[reader]]=]. + 1. If |reader| is undefined, return. + 1. [=Reject=] |reader|.[=ReadableStreamGenericReader/[[closedPromise]]=] with |e|. + 1. Set |reader|.[=ReadableStreamGenericReader/[[closedPromise]]=].\[[PromiseIsHandled]] to true. + 1. If |reader| [=implements=] {{ReadableStreamDefaultReader}}, + 1. Perform ! [$ReadableStreamDefaultReaderErrorReadRequests$](|reader|, |e|). + 1. Otherwise, + 1. Assert: |reader| [=implements=] {{ReadableStreamBYOBReader}}. + 1. Perform ! [$ReadableStreamBYOBReaderErrorReadIntoRequests$](|reader|, |e|). +
+ +
+ ReadableStreamFulfillReadIntoRequest(|stream|, + |chunk|, |done|) performs the following steps: + + 1. Assert: ! [$ReadableStreamHasBYOBReader$](|stream|) is true. + 1. Let |reader| be |stream|.[=ReadableStream/[[reader]]=]. + 1. Assert: |reader|.[=ReadableStreamBYOBReader/[[readIntoRequests]]=] is not [=list/is + empty|empty=]. + 1. Let |readIntoRequest| be |reader|.[=ReadableStreamBYOBReader/[[readIntoRequests]]=][0]. + 1. [=list/Remove=] |readIntoRequest| from + |reader|.[=ReadableStreamBYOBReader/[[readIntoRequests]]=]. + 1. If |done| is true, perform |readIntoRequest|'s [=read-into request/close steps=], given |chunk|. + 1. Otherwise, perform |readIntoRequest|'s [=read-into request/chunk steps=], given |chunk|. +
+ +
+ ReadableStreamFulfillReadRequest(|stream|, |chunk|, + |done|) performs the following steps: + + 1. Assert: ! [$ReadableStreamHasDefaultReader$](|stream|) is true. + 1. Let |reader| be |stream|.[=ReadableStream/[[reader]]=]. + 1. Assert: |reader|.[=ReadableStreamDefaultReader/[[readRequests]]=] is not [=list/is + empty|empty=]. + 1. Let |readRequest| be |reader|.[=ReadableStreamDefaultReader/[[readRequests]]=][0]. + 1. [=list/Remove=] |readRequest| from |reader|.[=ReadableStreamDefaultReader/[[readRequests]]=]. + 1. If |done| is true, perform |readRequest|'s [=read request/close steps=]. + 1. Otherwise, perform |readRequest|'s [=read request/chunk steps=], given |chunk|. +
+ +
+ ReadableStreamGetNumReadIntoRequests(|stream|) + performs the following steps: + + 1. Assert: ! [$ReadableStreamHasBYOBReader$](|stream|) is true. + 1. Return + |stream|.[=ReadableStream/[[reader]]=].[=ReadableStreamBYOBReader/[[readIntoRequests]]=]'s + [=list/size=]. +
+ +
+ ReadableStreamGetNumReadRequests(|stream|) + performs the following steps: + + 1. Assert: ! [$ReadableStreamHasDefaultReader$](|stream|) is true. + 1. Return |stream|.[=ReadableStream/[[reader]]=].[=ReadableStreamDefaultReader/[[readRequests]]=]'s + [=list/size=]. +
+ +
+ ReadableStreamHasBYOBReader(|stream|) performs the + following steps: + + 1. Let |reader| be |stream|.[=ReadableStream/[[reader]]=]. + 1. If |reader| is undefined, return false. + 1. If |reader| [=implements=] {{ReadableStreamBYOBReader}}, return true. + 1. Return false. +
+ +
+ ReadableStreamHasDefaultReader(|stream|) performs the + following steps: + + 1. Let |reader| be |stream|.[=ReadableStream/[[reader]]=]. + 1. If |reader| is undefined, return false. + 1. If |reader| [=implements=] {{ReadableStreamDefaultReader}}, return true. + 1. Return false. +
+ +

Readers

+ +The following abstract operations support the implementation and manipulation of +{{ReadableStreamDefaultReader}} and {{ReadableStreamBYOBReader}} instances. + +
+ ReadableStreamReaderGenericCancel(|reader|, + |reason|) performs the following steps: + + 1. Let |stream| be |reader|.[=ReadableStreamGenericReader/[[stream]]=]. + 1. Assert: |stream| is not undefined. + 1. Return ! [$ReadableStreamCancel$](|stream|, |reason|). +
+ +
+ ReadableStreamReaderGenericInitialize(|reader|, + |stream|) performs the following steps: + + 1. Set |reader|.[=ReadableStreamGenericReader/[[stream]]=] to |stream|. + 1. Set |stream|.[=ReadableStream/[[reader]]=] to |reader|. + 1. If |stream|.[=ReadableStream/[[state]]=] is "`readable`", + 1. Set |reader|.[=ReadableStreamGenericReader/[[closedPromise]]=] to [=a new promise=]. + 1. Otherwise, if |stream|.[=ReadableStream/[[state]]=] is "`closed`", + 1. Set |reader|.[=ReadableStreamGenericReader/[[closedPromise]]=] to [=a promise resolved with=] + undefined. + 1. Otherwise, + 1. Assert: |stream|.[=ReadableStream/[[state]]=] is "`errored`". + 1. Set |reader|.[=ReadableStreamGenericReader/[[closedPromise]]=] to [=a promise rejected with=] + |stream|.[=ReadableStream/[[storedError]]=]. + 1. Set |reader|.[=ReadableStreamGenericReader/[[closedPromise]]=].\[[PromiseIsHandled]] to true. +
+ +
+ ReadableStreamReaderGenericRelease(|reader|) + performs the following steps: + + 1. Let |stream| be |reader|.[=ReadableStreamGenericReader/[[stream]]=]. + 1. Assert: |stream| is not undefined. + 1. Assert: |stream|.[=ReadableStream/[[reader]]=] is |reader|. + 1. If |stream|.[=ReadableStream/[[state]]=] is "`readable`", [=reject=] + |reader|.[=ReadableStreamGenericReader/[[closedPromise]]=] with a {{TypeError}} exception. + 1. Otherwise, set |reader|.[=ReadableStreamGenericReader/[[closedPromise]]=] to [=a promise + rejected with=] a {{TypeError}} exception. + 1. Set |reader|.[=ReadableStreamGenericReader/[[closedPromise]]=].\[[PromiseIsHandled]] to true. + 1. Perform ! |stream|.[=ReadableStream/[[controller]]=].[$ReadableStreamController/[[ReleaseSteps]]$](). + 1. Set |stream|.[=ReadableStream/[[reader]]=] to undefined. + 1. Set |reader|.[=ReadableStreamGenericReader/[[stream]]=] to undefined. +
+ +
+ ReadableStreamBYOBReaderErrorReadIntoRequests(|reader|, |e|) + performs the following steps: + + 1. Let |readIntoRequests| be |reader|.[=ReadableStreamBYOBReader/[[readIntoRequests]]=]. + 1. Set |reader|.[=ReadableStreamBYOBReader/[[readIntoRequests]]=] to a new empty [=list=]. + 1. [=list/For each=] |readIntoRequest| of |readIntoRequests|, + 1. Perform |readIntoRequest|'s [=read-into request/error steps=], given |e|. +
+ +
+ ReadableStreamBYOBReaderRead(|reader|, |view|, |min|, + |readIntoRequest|) performs the following steps: + + 1. Let |stream| be |reader|.[=ReadableStreamGenericReader/[[stream]]=]. + 1. Assert: |stream| is not undefined. + 1. Set |stream|.[=ReadableStream/[[disturbed]]=] to true. + 1. If |stream|.[=ReadableStream/[[state]]=] is "`errored`", perform |readIntoRequest|'s [=read-into + request/error steps=] given |stream|.[=ReadableStream/[[storedError]]=]. + 1. Otherwise, perform ! [$ReadableByteStreamControllerPullInto$](|stream|.[=ReadableStream/[[controller]]=], + |view|, |min|, |readIntoRequest|). +
+ +
+ ReadableStreamBYOBReaderRelease(|reader|) + performs the following steps: + + 1. Perform ! [$ReadableStreamReaderGenericRelease$](|reader|). + 1. Let |e| be a new {{TypeError}} exception. + 1. Perform ! [$ReadableStreamBYOBReaderErrorReadIntoRequests$](|reader|, |e|). +
+ +
+ ReadableStreamDefaultReaderErrorReadRequests(|reader|, |e|) + performs the following steps: + + 1. Let |readRequests| be |reader|.[=ReadableStreamDefaultReader/[[readRequests]]=]. + 1. Set |reader|.[=ReadableStreamDefaultReader/[[readRequests]]=] to a new empty [=list=]. + 1. [=list/For each=] |readRequest| of |readRequests|, + 1. Perform |readRequest|'s [=read request/error steps=], given |e|. +
+ +
+ ReadableStreamDefaultReaderRead(|reader|, + |readRequest|) performs the following steps: + + 1. Let |stream| be |reader|.[=ReadableStreamGenericReader/[[stream]]=]. + 1. Assert: |stream| is not undefined. + 1. Set |stream|.[=ReadableStream/[[disturbed]]=] to true. + 1. If |stream|.[=ReadableStream/[[state]]=] is "`closed`", perform |readRequest|'s [=read + request/close steps=]. + 1. Otherwise, if |stream|.[=ReadableStream/[[state]]=] is "`errored`", perform |readRequest|'s + [=read request/error steps=] given |stream|.[=ReadableStream/[[storedError]]=]. + 1. Otherwise, + 1. Assert: |stream|.[=ReadableStream/[[state]]=] is "`readable`". + 1. Perform ! + |stream|.[=ReadableStream/[[controller]]=].[$ReadableStreamController/[[PullSteps]]$](|readRequest|). +
+ +
+ ReadableStreamDefaultReaderRelease(|reader|) + performs the following steps: + + 1. Perform ! [$ReadableStreamReaderGenericRelease$](|reader|). + 1. Let |e| be a new {{TypeError}} exception. + 1. Perform ! [$ReadableStreamDefaultReaderErrorReadRequests$](|reader|, |e|). +
+ +
+ SetUpReadableStreamBYOBReader(|reader|, |stream|) + performs the following steps: + + 1. If ! [$IsReadableStreamLocked$](|stream|) is true, throw a {{TypeError}} exception. + 1. If |stream|.[=ReadableStream/[[controller]]=] does not [=implement=] + {{ReadableByteStreamController}}, throw a {{TypeError}} exception. + 1. Perform ! [$ReadableStreamReaderGenericInitialize$](|reader|, |stream|). + 1. Set |reader|.[=ReadableStreamBYOBReader/[[readIntoRequests]]=] to a new empty [=list=]. +
+ +
+ SetUpReadableStreamDefaultReader(|reader|, + |stream|) performs the following steps: + + 1. If ! [$IsReadableStreamLocked$](|stream|) is true, throw a {{TypeError}} exception. + 1. Perform ! [$ReadableStreamReaderGenericInitialize$](|reader|, |stream|). + 1. Set |reader|.[=ReadableStreamDefaultReader/[[readRequests]]=] to a new empty [=list=]. +
+ +

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|.[=ReadableStreamDefaultController/[[pulling]]=] is true, + 1. Set |controller|.[=ReadableStreamDefaultController/[[pullAgain]]=] to true. + 1. Return. + 1. Assert: |controller|.[=ReadableStreamDefaultController/[[pullAgain]]=] is false. + 1. Set |controller|.[=ReadableStreamDefaultController/[[pulling]]=] to true. + 1. Let |pullPromise| be the result of performing + |controller|.[=ReadableStreamDefaultController/[[pullAlgorithm]]=]. + 1. [=Upon fulfillment=] of |pullPromise|, + 1. Set |controller|.[=ReadableStreamDefaultController/[[pulling]]=] to false. + 1. If |controller|.[=ReadableStreamDefaultController/[[pullAgain]]=] is true, + 1. Set |controller|.[=ReadableStreamDefaultController/[[pullAgain]]=] to false. + 1. Perform ! [$ReadableStreamDefaultControllerCallPullIfNeeded$](|controller|). + 1. [=Upon rejection=] of |pullPromise| with reason |e|, + 1. Perform ! [$ReadableStreamDefaultControllerError$](|controller|, |e|). +
+ +
+ ReadableStreamDefaultControllerShouldCallPull(|controller|) + performs the following steps: + + 1. Let |stream| be |controller|.[=ReadableStreamDefaultController/[[stream]]=]. + 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$](|controller|) is false, return false. + 1. If |controller|.[=ReadableStreamDefaultController/[[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|) + 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. + +

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

+ +
+ ReadableStreamDefaultControllerClose(|controller|) + performs the following steps: + + 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$](|controller|) is false, return. + 1. Let |stream| be |controller|.[=ReadableStreamDefaultController/[[stream]]=]. + 1. Set |controller|.[=ReadableStreamDefaultController/[[closeRequested]]=] to true. + 1. If |controller|.[=ReadableStreamDefaultController/[[queue]]=] [=list/is empty=], + 1. Perform ! [$ReadableStreamDefaultControllerClearAlgorithms$](|controller|). + 1. Perform ! [$ReadableStreamClose$](|stream|). +
+ +
+ ReadableStreamDefaultControllerEnqueue(|controller|, + |chunk|) performs the following steps: + + 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$](|controller|) is false, return. + 1. Let |stream| be |controller|.[=ReadableStreamDefaultController/[[stream]]=]. + 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|.[=ReadableStreamDefaultController/[[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|) performs the following steps: + + 1. Let |stream| be |controller|.[=ReadableStreamDefaultController/[[stream]]=]. + 1. If |stream|.[=ReadableStream/[[state]]=] is not "`readable`", return. + 1. Perform ! [$ResetQueue$](|controller|). + 1. Perform ! [$ReadableStreamDefaultControllerClearAlgorithms$](|controller|). + 1. Perform ! [$ReadableStreamError$](|stream|, |e|). +
+ +
+ ReadableStreamDefaultControllerGetDesiredSize(|controller|) + performs the following steps: + + 1. Let |state| be + |controller|.[=ReadableStreamDefaultController/[[stream]]=].[=ReadableStream/[[state]]=]. + 1. If |state| is "`errored`", return null. + 1. If |state| is "`closed`", return 0. + 1. Return |controller|.[=ReadableStreamDefaultController/[[strategyHWM]]=] − + |controller|.[=ReadableStreamDefaultController/[[queueTotalSize]]=]. +
+ +
+ ReadableStreamDefaultControllerHasBackpressure(|controller|) + is used in the implementation of {{TransformStream}}. It performs the following steps: + + 1. If ! [$ReadableStreamDefaultControllerShouldCallPull$](|controller|) is true, return false. + 1. Otherwise, return true. +
+ +
+ ReadableStreamDefaultControllerCanCloseOrEnqueue(|controller|) + performs the following steps: + + 1. Let |state| be + |controller|.[=ReadableStreamDefaultController/[[stream]]=].[=ReadableStream/[[state]]=]. + 1. If |controller|.[=ReadableStreamDefaultController/[[closeRequested]]=] is false and |state| is + "`readable`", return true. + 1. Otherwise, return false. + +

The case where |controller|.[=ReadableStreamDefaultController/[[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()}}. +

+ +
+ SetUpReadableStreamDefaultController(|stream|, + |controller|, |startAlgorithm|, |pullAlgorithm|, |cancelAlgorithm|, |highWaterMark|, + |sizeAlgorithm|) performs the following steps: + + 1. Assert: |stream|.[=ReadableStream/[[controller]]=] is undefined. + 1. Set |controller|.[=ReadableStreamDefaultController/[[stream]]=] to |stream|. + 1. Perform ! [$ResetQueue$](|controller|). + 1. Set |controller|.[=ReadableStreamDefaultController/[[started]]=], + |controller|.[=ReadableStreamDefaultController/[[closeRequested]]=], + |controller|.[=ReadableStreamDefaultController/[[pullAgain]]=], and + |controller|.[=ReadableStreamDefaultController/[[pulling]]=] to false. + 1. Set |controller|.[=ReadableStreamDefaultController/[[strategySizeAlgorithm]]=] to + |sizeAlgorithm| and |controller|.[=ReadableStreamDefaultController/[[strategyHWM]]=] to + |highWaterMark|. + 1. Set |controller|.[=ReadableStreamDefaultController/[[pullAlgorithm]]=] to |pullAlgorithm|. + 1. Set |controller|.[=ReadableStreamDefaultController/[[cancelAlgorithm]]=] to |cancelAlgorithm|. + 1. Set |stream|.[=ReadableStream/[[controller]]=] 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|.[=ReadableStreamDefaultController/[[started]]=] to true. + 1. Assert: |controller|.[=ReadableStreamDefaultController/[[pulling]]=] is false. + 1. Assert: |controller|.[=ReadableStreamDefaultController/[[pullAgain]]=] is false. + 1. Perform ! [$ReadableStreamDefaultControllerCallPullIfNeeded$](|controller|). + 1. [=Upon rejection=] of |startPromise| with reason |r|, + 1. Perform ! [$ReadableStreamDefaultControllerError$](|controller|, |r|). +
+ +
+ SetUpReadableStreamDefaultControllerFromUnderlyingSource(|stream|, + |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|). +
+ +

Byte stream controllers

+ +
+ ReadableByteStreamControllerCallPullIfNeeded(|controller|) + performs the following steps: + + 1. Let |shouldPull| be ! [$ReadableByteStreamControllerShouldCallPull$](|controller|). + 1. If |shouldPull| is false, return. + 1. If |controller|.[=ReadableByteStreamController/[[pulling]]=] is true, + 1. Set |controller|.[=ReadableByteStreamController/[[pullAgain]]=] to true. + 1. Return. + 1. Assert: |controller|.[=ReadableByteStreamController/[[pullAgain]]=] is false. + 1. Set |controller|.[=ReadableByteStreamController/[[pulling]]=] to true. + 1. Let |pullPromise| be the result of performing + |controller|.[=ReadableByteStreamController/[[pullAlgorithm]]=]. + 1. [=Upon fulfillment=] of |pullPromise|, + 1. Set |controller|.[=ReadableByteStreamController/[[pulling]]=] to false. + 1. If |controller|.[=ReadableByteStreamController/[[pullAgain]]=] is true, + 1. Set |controller|.[=ReadableByteStreamController/[[pullAgain]]=] to false. + 1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$](|controller|). + 1. [=Upon rejection=] of |pullPromise| with reason |e|, + 1. Perform ! [$ReadableByteStreamControllerError$](|controller|, |e|). +
+ +
+ 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. + +

This is observable using weak + references. See tc39/proposal-weakrefs#31 for more + detail. + + It performs the following steps: + + 1. Set |controller|.[=ReadableByteStreamController/[[pullAlgorithm]]=] to undefined. + 1. Set |controller|.[=ReadableByteStreamController/[[cancelAlgorithm]]=] to undefined. +

+ +
+ ReadableByteStreamControllerClearPendingPullIntos(|controller|) + performs the following steps: + + 1. Perform ! [$ReadableByteStreamControllerInvalidateBYOBRequest$](|controller|). + 1. Set |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=] to a new empty [=list=]. +
+ +
+ ReadableByteStreamControllerClose(|controller|) + performs the following steps: + + 1. Let |stream| be |controller|.[=ReadableByteStreamController/[[stream]]=]. + 1. If |controller|.[=ReadableByteStreamController/[[closeRequested]]=] is true or + |stream|.[=ReadableStream/[[state]]=] is not "`readable`", return. + 1. If |controller|.[=ReadableByteStreamController/[[queueTotalSize]]=] > 0, + 1. Set |controller|.[=ReadableByteStreamController/[[closeRequested]]=] to true. + 1. Return. + 1. If |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=] is not empty, + 1. Let |firstPendingPullInto| be + |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=][0]. + 1. If the remainder after dividing |firstPendingPullInto|'s [=pull-into descriptor/bytes filled=] + by |firstPendingPullInto|'s [=pull-into descriptor/element size=] is not 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|.[=ReadableStream/[[state]]=] is not "`errored`". + 1. Assert: |pullIntoDescriptor|.[=pull-into descriptor/reader type=] is not "`none`". + 1. Let |done| be false. + 1. If |stream|.[=ReadableStream/[[state]]=] is "`closed`", + 1. Assert: the remainder after dividing |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] + by |pullIntoDescriptor|'s [=pull-into descriptor/element size=] is 0. + 1. Set |done| to true. + 1. Let |filledView| be ! + [$ReadableByteStreamControllerConvertPullIntoDescriptor$](|pullIntoDescriptor|). + 1. If |pullIntoDescriptor|'s [=pull-into descriptor/reader type=] is "`default`", + 1. Perform ! [$ReadableStreamFulfillReadRequest$](|stream|, |filledView|, |done|). + 1. Otherwise, + 1. Assert: |pullIntoDescriptor|'s [=pull-into descriptor/reader type=] is "`byob`". + 1. Perform ! [$ReadableStreamFulfillReadIntoRequest$](|stream|, |filledView|, |done|). +
+ +
+ ReadableByteStreamControllerConvertPullIntoDescriptor(|pullIntoDescriptor|) + performs the following steps: + + 1. Let |bytesFilled| be |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=]. + 1. Let |elementSize| be |pullIntoDescriptor|'s [=pull-into descriptor/element size=]. + 1. Assert: |bytesFilled| ≤ |pullIntoDescriptor|'s [=pull-into descriptor/byte length=]. + 1. Assert: the remainder after dividing |bytesFilled| by |elementSize| is 0. + 1. Let |buffer| be ! [$TransferArrayBuffer$](|pullIntoDescriptor|'s [=pull-into descriptor/buffer=]). + 1. Return ! [$Construct$](|pullIntoDescriptor|'s [=pull-into descriptor/view constructor=], « + |buffer|, |pullIntoDescriptor|'s [=pull-into descriptor/byte offset=], + |bytesFilled| ÷ |elementSize| »). +
+ +
+ ReadableByteStreamControllerEnqueue(|controller|, + |chunk|) performs the following steps: + + 1. Let |stream| be |controller|.[=ReadableByteStreamController/[[stream]]=]. + 1. If |controller|.[=ReadableByteStreamController/[[closeRequested]]=] is true or + |stream|.[=ReadableStream/[[state]]=] is not "`readable`", return. + 1. Let |buffer| be |chunk|.\[[ViewedArrayBuffer]]. + 1. Let |byteOffset| be |chunk|.\[[ByteOffset]]. + 1. Let |byteLength| be |chunk|.\[[ByteLength]]. + 1. If ! [$IsDetachedBuffer$](|buffer|) is true, throw a {{TypeError}} exception. + 1. Let |transferredBuffer| be ? [$TransferArrayBuffer$](|buffer|). + 1. If |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=] is not + [=list/is empty|empty=], + 1. Let |firstPendingPullInto| be + |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=][0]. + 1. If ! [$IsDetachedBuffer$](|firstPendingPullInto|'s [=pull-into descriptor/buffer=]) + is true, throw a {{TypeError}} exception. + 1. Perform ! [$ReadableByteStreamControllerInvalidateBYOBRequest$](|controller|). + 1. Set |firstPendingPullInto|'s [=pull-into descriptor/buffer=] to ! + [$TransferArrayBuffer$](|firstPendingPullInto|'s [=pull-into descriptor/buffer=]). + 1. If |firstPendingPullInto|'s [=pull-into descriptor/reader type=] is "`none`", + perform ? [$ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue$](|controller|, + |firstPendingPullInto|). + 1. If ! [$ReadableStreamHasDefaultReader$](|stream|) is true, + 1. Perform ! [$ReadableByteStreamControllerProcessReadRequestsUsingQueue$](|controller|). + 1. If ! [$ReadableStreamGetNumReadRequests$](|stream|) is 0, + 1. Assert: |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=] is + [=list/is empty|empty=]. + 1. Perform ! [$ReadableByteStreamControllerEnqueueChunkToQueue$](|controller|, + |transferredBuffer|, |byteOffset|, |byteLength|). + 1. Otherwise, + 1. Assert: |controller|.[=ReadableByteStreamController/[[queue]]=] [=list/is empty=]. + 1. If |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=] is not + [=list/is empty|empty=], + 1. Assert: |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=][0]'s [=pull-into + descriptor/reader type=] is "`default`". + 1. Perform ! [$ReadableByteStreamControllerShiftPendingPullInto$](|controller|). + 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|) performs the following steps: + + 1. [=list/Append=] a new [=readable byte stream queue entry=] with [=readable byte stream queue + entry/buffer=] |buffer|, [=readable byte stream queue entry/byte offset=] |byteOffset|, and + [=readable byte stream queue entry/byte length=] |byteLength| to + |controller|.[=ReadableByteStreamController/[[queue]]=]. + 1. Set |controller|.[=ReadableByteStreamController/[[queueTotalSize]]=] to + |controller|.[=ReadableByteStreamController/[[queueTotalSize]]=] + |byteLength|. +
+ +
+ ReadableByteStreamControllerEnqueueClonedChunkToQueue(|controller|, + |buffer|, |byteOffset|, |byteLength|) performs the following steps: + + 1. Let |cloneResult| be [$CloneArrayBuffer$](|buffer|, |byteOffset|, |byteLength|, {{%ArrayBuffer%}}). + 1. If |cloneResult| is an abrupt completion, + 1. Perform ! [$ReadableByteStreamControllerError$](|controller|, |cloneResult|.\[[Value]]). + 1. Return |cloneResult|. + 1. Perform ! [$ReadableByteStreamControllerEnqueueChunkToQueue$](|controller|, + |cloneResult|.\[[Value]], 0, |byteLength|). +
+ +
+ ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(|controller|, + |pullIntoDescriptor|) performs the following steps: + + 1. Assert: |pullIntoDescriptor|'s [=pull-into descriptor/reader type=] is "`none`". + 1. If |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] > 0, perform ? + [$ReadableByteStreamControllerEnqueueClonedChunkToQueue$](|controller|, |pullIntoDescriptor|'s + [=pull-into descriptor/buffer=], |pullIntoDescriptor|'s [=pull-into descriptor/byte offset=], + |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=]). + 1. Perform ! [$ReadableByteStreamControllerShiftPendingPullInto$](|controller|). +
+ +
+ ReadableByteStreamControllerError(|controller|, + |e|) performs the following steps: + + 1. Let |stream| be |controller|.[=ReadableByteStreamController/[[stream]]=]. + 1. If |stream|.[=ReadableStream/[[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|.[=ReadableByteStreamController/[[pendingPullIntos]]=] + [=list/is empty=], or |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=][0] + is |pullIntoDescriptor|. + 1. Assert: |controller|.[=ReadableByteStreamController/[[byobRequest]]=] is null. + 1. Set |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] to [=pull-into + descriptor/bytes filled=] + |size|. +
+ +
+ ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(|controller|, + |pullIntoDescriptor|) performs the following steps: + + 1. Let |maxBytesToCopy| be min(|controller|.[=ReadableByteStreamController/[[queueTotalSize]]=], + |pullIntoDescriptor|'s [=pull-into descriptor/byte length=] − |pullIntoDescriptor|'s [=pull-into + descriptor/bytes filled=]). + 1. Let |maxBytesFilled| be |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] + + |maxBytesToCopy|. + 1. Let |totalBytesToCopyRemaining| be |maxBytesToCopy|. + 1. Let |ready| be false. + 1. Assert: |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] < |pullIntoDescriptor|'s + [=pull-into descriptor/minimum fill=]. + 1. Let |remainderBytes| be the remainder after dividing |maxBytesFilled| by |pullIntoDescriptor|'s + [=pull-into descriptor/element size=]. + 1. Let |maxAlignedBytes| be |maxBytesFilled| − |remainderBytes|. + 1. If |maxAlignedBytes| ≥ |pullIntoDescriptor|'s [=pull-into descriptor/minimum fill=], + 1. Set |totalBytesToCopyRemaining| to |maxAlignedBytes| − |pullIntoDescriptor|'s [=pull-into + descriptor/bytes filled=]. + 1. Set |ready| to true. +

A descriptor for a {{ReadableStreamBYOBReader/read()}} request + that is not yet filled up to its minimum length will stay at the head of the queue, so the + [=underlying source=] can keep filling it. + 1. Let |queue| be |controller|.[=ReadableByteStreamController/[[queue]]=]. + 1. [=While=] |totalBytesToCopyRemaining| > 0, + 1. Let |headOfQueue| be |queue|[0]. + 1. Let |bytesToCopy| be min(|totalBytesToCopyRemaining|, |headOfQueue|'s [=readable byte stream + queue entry/byte length=]). + 1. Let |destStart| be |pullIntoDescriptor|'s [=pull-into descriptor/byte offset=] + + |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=]. + 1. Perform ! [$CopyDataBlockBytes$](|pullIntoDescriptor|'s [=pull-into + descriptor/buffer=].\[[ArrayBufferData]], |destStart|, + |headOfQueue|'s [=readable byte stream queue entry/buffer=].\[[ArrayBufferData]], + |headOfQueue|'s [=readable byte stream queue entry/byte offset=], |bytesToCopy|). + 1. If |headOfQueue|'s [=readable byte stream queue entry/byte length=] is |bytesToCopy|, + 1. [=list/Remove=] |queue|[0]. + 1. Otherwise, + 1. Set |headOfQueue|'s [=readable byte stream queue entry/byte offset=] to |headOfQueue|'s + [=readable byte stream queue entry/byte offset=] + |bytesToCopy|. + 1. Set |headOfQueue|'s [=readable byte stream queue entry/byte length=] to |headOfQueue|'s + [=readable byte stream queue entry/byte length=] − |bytesToCopy|. + 1. Set |controller|.[=ReadableByteStreamController/[[queueTotalSize]]=] to + |controller|.[=ReadableByteStreamController/[[queueTotalSize]]=] − |bytesToCopy|. + 1. Perform ! [$ReadableByteStreamControllerFillHeadPullIntoDescriptor$](|controller|, + |bytesToCopy|, |pullIntoDescriptor|). + 1. Set |totalBytesToCopyRemaining| to |totalBytesToCopyRemaining| − |bytesToCopy|. + 1. If |ready| is false, + 1. Assert: |controller|.[=ReadableByteStreamController/[[queueTotalSize]]=] is 0. + 1. Assert: |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] > 0. + 1. Assert: |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] < + |pullIntoDescriptor|'s [=pull-into descriptor/minimum fill=]. + 1. Return |ready|. +

+ +
+ ReadableByteStreamControllerFillReadRequestFromQueue(|controller|, + |readRequest|) performs the following steps: + + 1. Assert: |controller|.[=ReadableByteStreamController/[[queueTotalSize]]=] > 0. + 1. Let |entry| be |controller|.[=ReadableByteStreamController/[[queue]]=][0]. + 1. [=list/Remove=] |entry| from |controller|.[=ReadableByteStreamController/[[queue]]=]. + 1. Set |controller|.[=ReadableByteStreamController/[[queueTotalSize]]=] to + |controller|.[=ReadableByteStreamController/[[queueTotalSize]]=] − |entry|'s [=readable byte stream + queue entry/byte length=]. + 1. Perform ! [$ReadableByteStreamControllerHandleQueueDrain$](|controller|). + 1. Let |view| be ! [$Construct$]({{%Uint8Array%}}, « |entry|'s [=readable byte stream queue + entry/buffer=], |entry|'s [=readable byte stream queue entry/byte offset=], |entry|'s + [=readable byte stream queue entry/byte length=] »). + 1. Perform |readRequest|'s [=read request/chunk steps=], given |view|. +
+ +
+ ReadableByteStreamControllerGetBYOBRequest(|controller|) performs + the following steps: + + 1. If |controller|.[=ReadableByteStreamController/[[byobRequest]]=] is null and + |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=] is not [=list/is empty|empty=], + 1. Let |firstDescriptor| be |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=][0]. + 1. Let |view| be ! [$Construct$]({{%Uint8Array%}}, « |firstDescriptor|'s [=pull-into + descriptor/buffer=], |firstDescriptor|'s [=pull-into descriptor/byte offset=] + + |firstDescriptor|'s [=pull-into descriptor/bytes filled=], |firstDescriptor|'s [=pull-into + descriptor/byte length=] − |firstDescriptor|'s [=pull-into descriptor/bytes filled=] »). + 1. Let |byobRequest| be a [=new=] {{ReadableStreamBYOBRequest}}. + 1. Set |byobRequest|.[=ReadableStreamBYOBRequest/[[controller]]=] to |controller|. + 1. Set |byobRequest|.[=ReadableStreamBYOBRequest/[[view]]=] to |view|. + 1. Set |controller|.[=ReadableByteStreamController/[[byobRequest]]=] to |byobRequest|. + 1. Return |controller|.[=ReadableByteStreamController/[[byobRequest]]=]. +
+ +
+ ReadableByteStreamControllerGetDesiredSize(|controller|) + performs the following steps: + + 1. Let |state| be |controller|.[=ReadableByteStreamController/[[stream]]=].[=ReadableStream/[[state]]=]. + 1. If |state| is "`errored`", return null. + 1. If |state| is "`closed`", return 0. + 1. Return |controller|.[=ReadableByteStreamController/[[strategyHWM]]=] − + |controller|.[=ReadableByteStreamController/[[queueTotalSize]]=]. +
+ +
+ ReadableByteStreamControllerHandleQueueDrain(|controller|) + performs the following steps: + + 1. Assert: |controller|.[=ReadableByteStreamController/[[stream]]=].[=ReadableStream/[[state]]=] is + "`readable`". + 1. If |controller|.[=ReadableByteStreamController/[[queueTotalSize]]=] is 0 and + |controller|.[=ReadableByteStreamController/[[closeRequested]]=] is true, + 1. Perform ! [$ReadableByteStreamControllerClearAlgorithms$](|controller|). + 1. Perform ! [$ReadableStreamClose$](|controller|.[=ReadableByteStreamController/[[stream]]=]). + 1. Otherwise, + 1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$](|controller|). +
+ +
+ ReadableByteStreamControllerInvalidateBYOBRequest(|controller|) + performs the following steps: + + 1. If |controller|.[=ReadableByteStreamController/[[byobRequest]]=] is null, return. + 1. Set + |controller|.[=ReadableByteStreamController/[[byobRequest]]=].[=ReadableStreamBYOBRequest/[[controller]]=] + to undefined. + 1. Set + |controller|.[=ReadableByteStreamController/[[byobRequest]]=].[=ReadableStreamBYOBRequest/[[view]]=] + to null. + 1. Set |controller|.[=ReadableByteStreamController/[[byobRequest]]=] to null. +
+ +
+ ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(|controller|) + performs the following steps: + + 1. Assert: |controller|.[=ReadableByteStreamController/[[closeRequested]]=] is false. + 1. [=While=] |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=] is not + [=list/is empty|empty=], + 1. If |controller|.[=ReadableByteStreamController/[[queueTotalSize]]=] is 0, return. + 1. Let |pullIntoDescriptor| be + |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=][0]. + 1. If ! [$ReadableByteStreamControllerFillPullIntoDescriptorFromQueue$](|controller|, + |pullIntoDescriptor|) is true, + 1. Perform ! [$ReadableByteStreamControllerShiftPendingPullInto$](|controller|). + 1. Perform ! + [$ReadableByteStreamControllerCommitPullIntoDescriptor$](|controller|.[=ReadableByteStreamController/[[stream]]=], + |pullIntoDescriptor|). +
+ +
+ ReadableByteStreamControllerProcessReadRequestsUsingQueue(|controller|) + performs the following steps: + + 1. Let |reader| be |controller|.[=ReadableByteStreamController/[[stream]]=].[=ReadableStream/[[reader]]=]. + 1. Assert: |reader| [=implements=] {{ReadableStreamDefaultReader}}. + 1. While |reader|.[=ReadableStreamDefaultReader/[[readRequests]]=] is not [=list/is empty|empty=], + 1. If |controller|.[=ReadableByteStreamController/[[queueTotalSize]]=] is 0, return. + 1. Let |readRequest| be |reader|.[=ReadableStreamDefaultReader/[[readRequests]]=][0]. + 1. [=list/Remove=] |readRequest| from |reader|.[=ReadableStreamDefaultReader/[[readRequests]]=]. + 1. Perform ! [$ReadableByteStreamControllerFillReadRequestFromQueue$](|controller|, |readRequest|). +
+ +
+ ReadableByteStreamControllerPullInto(|controller|, + |view|, |min|, |readIntoRequest|) performs the following steps: + + 1. Let |stream| be |controller|.[=ReadableByteStreamController/[[stream]]=]. + 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 |minimumFill| be |min| × |elementSize|. + 1. Assert: |minimumFill| ≥ 0 and |minimumFill| ≤ |view|.\[[ByteLength]]. + 1. Assert: the remainder after dividing |minimumFill| by |elementSize| is 0. + 1. Let |byteOffset| be |view|.\[[ByteOffset]]. + 1. Let |byteLength| be |view|.\[[ByteLength]]. + 1. Let |bufferResult| be [$TransferArrayBuffer$](|view|.\[[ViewedArrayBuffer]]). + 1. If |bufferResult| is an abrupt completion, + 1. Perform |readIntoRequest|'s [=read-into request/error steps=], given |bufferResult|.\[[Value]]. + 1. Return. + 1. Let |buffer| be |bufferResult|.\[[Value]]. + 1. Let |pullIntoDescriptor| be a new [=pull-into descriptor=] with +
+
[=pull-into descriptor/buffer=] +
|buffer| + +
[=pull-into descriptor/buffer byte length=] +
|buffer|.\[[ArrayBufferByteLength]] + +
[=pull-into descriptor/byte offset=] +
|byteOffset| + +
[=pull-into descriptor/byte length=] +
|byteLength| + +
[=pull-into descriptor/bytes filled=] +
0 + +
[=pull-into descriptor/minimum fill=] +
|minimumFill| + +
[=pull-into descriptor/element size=] +
|elementSize| + +
[=pull-into descriptor/view constructor=] +
|ctor| + +
[=pull-into descriptor/reader type=] +
"`byob`" +
+ 1. If |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=] is not empty, + 1. [=list/Append=] |pullIntoDescriptor| to + |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=]. + 1. Perform ! [$ReadableStreamAddReadIntoRequest$](|stream|, |readIntoRequest|). + 1. Return. + 1. If |stream|.[=ReadableStream/[[state]]=] is "`closed`", + 1. Let |emptyView| be ! [$Construct$](|ctor|, « |pullIntoDescriptor|'s [=pull-into + descriptor/buffer=], |pullIntoDescriptor|'s [=pull-into descriptor/byte offset=], 0 »). + 1. Perform |readIntoRequest|'s [=read-into request/close steps=], given |emptyView|. + 1. Return. + 1. If |controller|.[=ReadableByteStreamController/[[queueTotalSize]]=] > 0, + 1. If ! [$ReadableByteStreamControllerFillPullIntoDescriptorFromQueue$](|controller|, + |pullIntoDescriptor|) is true, + 1. Let |filledView| be ! + [$ReadableByteStreamControllerConvertPullIntoDescriptor$](|pullIntoDescriptor|). + 1. Perform ! [$ReadableByteStreamControllerHandleQueueDrain$](|controller|). + 1. Perform |readIntoRequest|'s [=read-into request/chunk steps=], given |filledView|. + 1. Return. + 1. If |controller|.[=ReadableByteStreamController/[[closeRequested]]=] is true, + 1. Let |e| be a {{TypeError}} exception. + 1. Perform ! [$ReadableByteStreamControllerError$](|controller|, |e|). + 1. Perform |readIntoRequest|'s [=read-into request/error steps=], given |e|. + 1. Return. + 1. [=list/Append=] |pullIntoDescriptor| to + |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=]. + 1. Perform ! [$ReadableStreamAddReadIntoRequest$](|stream|, |readIntoRequest|). + 1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$](|controller|). +
+ +
+ ReadableByteStreamControllerRespond(|controller|, + |bytesWritten|) performs the following steps: + + 1. Assert: |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=] is not empty. + 1. Let |firstDescriptor| be |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=][0]. + 1. Let |state| be + |controller|.[=ReadableByteStreamController/[[stream]]=].[=ReadableStream/[[state]]=]. + 1. If |state| is "`closed`", + 1. If |bytesWritten| is not 0, throw a {{TypeError}} exception. + 1. Otherwise, + 1. Assert: |state| is "`readable`". + 1. If |bytesWritten| is 0, throw a {{TypeError}} exception. + 1. If |firstDescriptor|'s [=pull-into descriptor/bytes filled=] + |bytesWritten| > + |firstDescriptor|'s [=pull-into descriptor/byte length=], throw a {{RangeError}} exception. + 1. Set |firstDescriptor|'s [=pull-into descriptor/buffer=] to ! + [$TransferArrayBuffer$](|firstDescriptor|'s [=pull-into descriptor/buffer=]). + 1. Perform ? [$ReadableByteStreamControllerRespondInternal$](|controller|, |bytesWritten|). +
+ +
+ ReadableByteStreamControllerRespondInClosedState(|controller|, + |firstDescriptor|) performs the following steps: + + 1. Assert: the remainder after dividing |firstDescriptor|'s [=pull-into descriptor/bytes filled=] + by |firstDescriptor|'s [=pull-into descriptor/element size=] is 0. + 1. If |firstDescriptor|'s [=pull-into descriptor/reader type=] is "`none`", + perform ! [$ReadableByteStreamControllerShiftPendingPullInto$](|controller|). + 1. Let |stream| be |controller|.[=ReadableByteStreamController/[[stream]]=]. + 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. Assert: |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] + |bytesWritten| ≤ + |pullIntoDescriptor|'s [=pull-into descriptor/byte length=]. + 1. Perform ! [$ReadableByteStreamControllerFillHeadPullIntoDescriptor$](|controller|, + |bytesWritten|, |pullIntoDescriptor|). + 1. If |pullIntoDescriptor|'s [=pull-into descriptor/reader type=] is "`none`", + 1. Perform ? [$ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue$](|controller|, + |pullIntoDescriptor|). + 1. Perform ! [$ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue$](|controller|). + 1. Return. + 1. If |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] < |pullIntoDescriptor|'s + [=pull-into descriptor/minimum fill=], return. +

A descriptor for a {{ReadableStreamBYOBReader/read()}} request + that is not yet filled up to its minimum length will stay at the head of the queue, so the + [=underlying source=] can keep filling it. + 1. Perform ! [$ReadableByteStreamControllerShiftPendingPullInto$](|controller|). + 1. Let |remainderSize| be the remainder after dividing |pullIntoDescriptor|'s + [=pull-into descriptor/bytes filled=] by |pullIntoDescriptor|'s [=pull-into descriptor/element size=]. + 1. If |remainderSize| > 0, + 1. Let |end| be |pullIntoDescriptor|'s [=pull-into descriptor/byte offset=] + + |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=]. + 1. Perform ? [$ReadableByteStreamControllerEnqueueClonedChunkToQueue$](|controller|, + |pullIntoDescriptor|'s [=pull-into descriptor/buffer=], |end| − |remainderSize|, + |remainderSize|). + 1. Set |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] to |pullIntoDescriptor|'s + [=pull-into descriptor/bytes filled=] − |remainderSize|. + 1. Perform ! + [$ReadableByteStreamControllerCommitPullIntoDescriptor$](|controller|.[=ReadableByteStreamController/[[stream]]=], + |pullIntoDescriptor|). + 1. Perform ! [$ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue$](|controller|). +

+ +
+ ReadableByteStreamControllerRespondInternal(|controller|, + |bytesWritten|) performs the following steps: + + 1. Let |firstDescriptor| be |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=][0]. + 1. Assert: ! [$CanTransferArrayBuffer$](|firstDescriptor|'s [=pull-into descriptor/buffer=]) is true. + 1. Perform ! [$ReadableByteStreamControllerInvalidateBYOBRequest$](|controller|). + 1. Let |state| be + |controller|.[=ReadableByteStreamController/[[stream]]=].[=ReadableStream/[[state]]=]. + 1. If |state| is "`closed`", + 1. Assert: |bytesWritten| is 0. + 1. Perform ! [$ReadableByteStreamControllerRespondInClosedState$](|controller|, + |firstDescriptor|). + 1. Otherwise, + 1. Assert: |state| is "`readable`". + 1. Assert: |bytesWritten| > 0. + 1. Perform ? [$ReadableByteStreamControllerRespondInReadableState$](|controller|, |bytesWritten|, + |firstDescriptor|). + 1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$](|controller|). +
+ +
+ ReadableByteStreamControllerRespondWithNewView(|controller|, + |view|) performs the following steps: + + 1. Assert: |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=] is not [=list/is + empty|empty=]. + 1. Assert: ! [$IsDetachedBuffer$](|view|.\[[ViewedArrayBuffer]]) is false. + 1. Let |firstDescriptor| be |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=][0]. + 1. Let |state| be + |controller|.[=ReadableByteStreamController/[[stream]]=].[=ReadableStream/[[state]]=]. + 1. If |state| is "`closed`", + 1. If |view|.\[[ByteLength]] is not 0, throw a {{TypeError}} exception. + 1. Otherwise, + 1. Assert: |state| is "`readable`". + 1. If |view|.\[[ByteLength]] is 0, throw a {{TypeError}} exception. + 1. If |firstDescriptor|'s [=pull-into descriptor/byte offset=] + |firstDescriptor|' [=pull-into + descriptor/bytes filled=] is not |view|.\[[ByteOffset]], throw a {{RangeError}} exception. + 1. If |firstDescriptor|'s [=pull-into descriptor/buffer byte length=] is not + |view|.\[[ViewedArrayBuffer]].\[[ByteLength]], throw a {{RangeError}} exception. + 1. If |firstDescriptor|'s [=pull-into descriptor/bytes filled=] + |view|.\[[ByteLength]] > + |firstDescriptor|'s [=pull-into descriptor/byte length=], throw a {{RangeError}} exception. + 1. Let |viewByteLength| be |view|.\[[ByteLength]]. + 1. Set |firstDescriptor|'s [=pull-into descriptor/buffer=] to ? + [$TransferArrayBuffer$](|view|.\[[ViewedArrayBuffer]]). + 1. Perform ? [$ReadableByteStreamControllerRespondInternal$](|controller|, |viewByteLength|). +
+ +
+ ReadableByteStreamControllerShiftPendingPullInto(|controller|) + performs the following steps: + + 1. Assert: |controller|.[=ReadableByteStreamController/[[byobRequest]]=] is null. + 1. Let |descriptor| be |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=][0]. + 1. [=list/Remove=] |descriptor| from + |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=]. + 1. Return |descriptor|. +
+ +
+ ReadableByteStreamControllerShouldCallPull(|controller|) + performs the following steps: + + 1. Let |stream| be |controller|.[=ReadableByteStreamController/[[stream]]=]. + 1. If |stream|.[=ReadableStream/[[state]]=] is not "`readable`", return false. + 1. If |controller|.[=ReadableByteStreamController/[[closeRequested]]=] is true, return false. + 1. If |controller|.[=ReadableByteStreamController/[[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|.[=ReadableStream/[[controller]]=] is undefined. + 1. If |autoAllocateChunkSize| is not undefined, + 1. Assert: ! [$IsInteger$](|autoAllocateChunkSize|) is true. + 1. Assert: |autoAllocateChunkSize| is positive. + 1. Set |controller|.[=ReadableByteStreamController/[[stream]]=] to |stream|. + 1. Set |controller|.[=ReadableByteStreamController/[[pullAgain]]=] and + |controller|.[=ReadableByteStreamController/[[pulling]]=] to false. + 1. Set |controller|.[=ReadableByteStreamController/[[byobRequest]]=] to null. + 1. Perform ! [$ResetQueue$](|controller|). + 1. Set |controller|.[=ReadableByteStreamController/[[closeRequested]]=] and + |controller|.[=ReadableByteStreamController/[[started]]=] to false. + 1. Set |controller|.[=ReadableByteStreamController/[[strategyHWM]]=] to |highWaterMark|. + 1. Set |controller|.[=ReadableByteStreamController/[[pullAlgorithm]]=] to |pullAlgorithm|. + 1. Set |controller|.[=ReadableByteStreamController/[[cancelAlgorithm]]=] to |cancelAlgorithm|. + 1. Set |controller|.[=ReadableByteStreamController/[[autoAllocateChunkSize]]=] to + |autoAllocateChunkSize|. + 1. Set |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=] to a new empty [=list=]. + 1. Set |stream|.[=ReadableStream/[[controller]]=] 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|.[=ReadableByteStreamController/[[started]]=] to true. + 1. Assert: |controller|.[=ReadableByteStreamController/[[pulling]]=] is false. + 1. Assert: |controller|.[=ReadableByteStreamController/[[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. If |autoAllocateChunkSize| is 0, then throw a {{TypeError}} exception. + 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=*, Transferable] +interface WritableStream { + constructor(optional object underlyingSink, optional QueuingStrategy strategy = {}); + + readonly attribute boolean locked; + + Promise<undefined> abort(optional any reason); + Promise<undefined> close(); + WritableStreamDefaultWriter getWriter(); +}; + + +

Internal slots

+ +Instances of {{WritableStream}} are created with the internal slots described in the following +table: + + + + + + + + + + + + + + + + +
Internal Slot + Description (non-normative) +
\[[backpressure]] + A boolean indicating the backpressure signal set by the controller +
\[[closeRequest]] + The promise returned from the writer's + {{WritableStreamDefaultWriter/close()}} method +
\[[controller]] + A {{WritableStreamDefaultController}} created with the ability to + control the state and queue of this stream +
\[[Detached]] + A boolean flag set to true when the stream is transferred +
\[[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 [=pending abort request=] +
\[[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 +
\[[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=] +
+ +

The [=WritableStream/[[inFlightCloseRequest]]=] slot and +[=WritableStream/[[closeRequest]]=] slot are mutually exclusive. Similarly, no element will be +removed from [=WritableStream/[[writeRequests]]=] while [=WritableStream/[[inFlightWriteRequest]]=] +is not undefined. Implementations can optimize storage for these slots based on these invariants. + +A pending abort request is a [=struct=] used to track a request to abort the stream +before that request is finally processed. It has the following [=struct/items=]: + +: promise +:: A promise returned from [$WritableStreamAbort$] +: reason +:: A JavaScript value that was passed as the abort reason to [$WritableStreamAbort$] +: was already erroring +:: A boolean indicating whether or not the stream was in the "`erroring`" state when + [$WritableStreamAbort$] was called, which impacts the outcome of the abort request + +

The underlying sink API

+ +The {{WritableStream()}} constructor accepts as its first argument a JavaScript object representing +the [=underlying sink=]. Such objects can contain any of the following properties: + + +dictionary UnderlyingSink { + UnderlyingSinkStartCallback start; + UnderlyingSinkWriteCallback write; + UnderlyingSinkCloseCallback close; + UnderlyingSinkAbortCallback abort; + any type; +}; + +callback UnderlyingSinkStartCallback = any (WritableStreamDefaultController controller); +callback UnderlyingSinkWriteCallback = Promise<undefined> (any chunk, WritableStreamDefaultController controller); +callback UnderlyingSinkCloseCallback = Promise<undefined> (); +callback UnderlyingSinkAbortCallback = Promise<undefined> (optional any reason); + + +
+
start(controller)
+
+

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

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

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

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

Finally, the promise potentially returned by this function is used to ensure that well-behaved [=producers=] do not attempt to mutate the + [=chunk=] before it has been fully processed. (This is not guaranteed by any specification + machinery, but instead is an informal contract between [=producers=] and the [=underlying + sink=].) + +

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

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

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

abort(reason)
+
+

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

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

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

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

type
+
+

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

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

Constructor, methods, and properties

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ +
+ The locked getter steps are: + + 1. Return ! [$IsWritableStreamLocked$]([=this=]). +
+ +
+ 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|). +
+ +
+ The close() method steps are: + + 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=]). +
+ +
+ The getWriter() method steps are: + + 1. Return ? [$AcquireWritableStreamDefaultWriter$]([=this=]). +
+ +

Transfer via `postMessage()`

+ +
+
destination.postMessage(ws, { transfer: [ws] }); +
+

Sends a {{WritableStream}} to another frame, window, or worker. + +

The transferred stream can be used exactly like the original. The original will become + [=locked to a writer|locked=] and no longer directly usable. +

+
+ +
+ {{WritableStream}} objects are [=transferable objects=]. Their [=transfer steps=], given |value| + and |dataHolder|, are: + + 1. If ! [$IsWritableStreamLocked$](|value|) is true, throw a "{{DataCloneError}}" {{DOMException}}. + 1. Let |port1| be a [=new=] {{MessagePort}} in [=the current Realm=]. + 1. Let |port2| be a [=new=] {{MessagePort}} in [=the current Realm=]. + 1. [=Entangle=] |port1| and |port2|. + 1. Let |readable| be a [=new=] {{ReadableStream}} in [=the current Realm=]. + 1. Perform ! [$SetUpCrossRealmTransformReadable$](|readable|, |port1|). + 1. Let |promise| be ! [$ReadableStreamPipeTo$](|readable|, |value|, false, false, false). + 1. Set |promise|.\[[PromiseIsHandled]] to true. + 1. Set |dataHolder|.\[[port]] to ! [$StructuredSerializeWithTransfer$](|port2|, « |port2| »). +
+ +
+ Their [=transfer-receiving steps=], given |dataHolder| and |value|, are: + + 1. Let |deserializedRecord| be ! [$StructuredDeserializeWithTransfer$](|dataHolder|.\[[port]], + [=the current Realm=]). + 1. Let |port| be a |deserializedRecord|.\[[Deserialized]]. + 1. Perform ! [$SetUpCrossRealmTransformWritable$](|value|, |port|). +
+ +

The {{WritableStreamDefaultWriter}} class

+ +The {{WritableStreamDefaultWriter}} class represents a [=writable stream writer=] designed to be +vended by a {{WritableStream}} instance. + +

Interface definition

+ +The Web IDL definition for the {{WritableStreamDefaultWriter}} class is given as follows: + + +[Exposed=*] +interface WritableStreamDefaultWriter { + constructor(WritableStream stream); + + readonly attribute Promise<undefined> closed; + readonly attribute unrestricted double? desiredSize; + readonly attribute Promise<undefined> ready; + + Promise<undefined> abort(optional any reason); + Promise<undefined> close(); + undefined releaseLock(); + Promise<undefined> 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 +
\[[readyPromise]] + A promise returned by the writer's + {{WritableStreamDefaultWriter/ready}} getter +
\[[stream]] + A {{WritableStream}} instance that owns this reader +
+ +

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

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

If chunk is mutable, [=producers=] are advised to + avoid mutating it after passing it to {{WritableStreamDefaultWriter/write()}}, until after the + promise returned by {{WritableStreamDefaultWriter/write()}} settles. This ensures that the + [=underlying sink=] receives and processes the same value that was passed in. +

+ +
+ The new WritableStreamDefaultWriter(|stream|) + constructor steps are: + + 1. Perform ? [$SetUpWritableStreamDefaultWriter$]([=this=], |stream|). +
+ +
+ The closed + getter steps are: + + 1. Return [=this=].[=WritableStreamDefaultWriter/[[closedPromise]]=]. +
+ +
+ The desiredSize getter steps are: + + 1. If [=this=].[=WritableStreamDefaultWriter/[[stream]]=] is undefined, throw a {{TypeError}} + exception. + 1. Return ! [$WritableStreamDefaultWriterGetDesiredSize$]([=this=]). +
+ +
+ The ready getter + steps are: + + 1. Return [=this=].[=WritableStreamDefaultWriter/[[readyPromise]]=]. +
+ +
+ The abort(|reason|) + method steps are: + + 1. If [=this=].[=WritableStreamDefaultWriter/[[stream]]=] is undefined, return [=a promise rejected + with=] a {{TypeError}} exception. + 1. Return ! [$WritableStreamDefaultWriterAbort$]([=this=], |reason|). +
+ +
+ The close() method + steps are: + + 1. Let |stream| be [=this=].[=WritableStreamDefaultWriter/[[stream]]=]. + 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=].[=WritableStreamDefaultWriter/[[stream]]=]. + 1. If |stream| is undefined, return. + 1. Assert: |stream|.[=WritableStream/[[writer]]=] is not undefined. + 1. Perform ! [$WritableStreamDefaultWriterRelease$]([=this=]). +
+ +
+ The write(|chunk|) + method steps are: + + 1. If [=this=].[=WritableStreamDefaultWriter/[[stream]]=] 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=*] +interface WritableStreamDefaultController { + readonly attribute AbortSignal signal; + undefined error(optional any e); +}; + + +

Internal slots

+ +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=] +
\[[abortController]] + An {{AbortController}} that can be used to abort the pending write or + close operation when the stream is [=abort a writable stream|aborted=]. +
\[[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=] +
\[[queueTotalSize]] + The total size of all the chunks stored in + [=WritableStreamDefaultController/[[queue]]=] (see [[#queue-with-sizes]]) +
\[[started]] + A boolean flag indicating whether the [=underlying sink=] has finished + starting +
\[[strategyHWM]] + A number supplied by the creator of the stream as part of the stream's + [=queuing strategy=], indicating the point at which the stream will apply [=backpressure=] to its + [=underlying sink=] +
\[[strategySizeAlgorithm]] + An algorithm to calculate the size of enqueued [=chunks=], as part of + the stream's [=queuing strategy=] +
\[[stream]] + The {{WritableStream}} instance controlled +
\[[writeAlgorithm]] + A promise-returning algorithm, taking one argument (the [=chunk=] to + write), which writes data to the [=underlying sink=] +
+ +The close sentinel is a unique value enqueued into +[=WritableStreamDefaultController/[[queue]]=], in lieu of a [=chunk=], to signal that the stream is +closed. It is only used internally, and is never exposed to web developers. + +

Methods and properties

+ +
+
controller.{{WritableStreamDefaultController/signal}} +
+

An AbortSignal that can be used to abort the pending write or close operation when the stream is + [=abort a writable stream|aborted=]. +

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 signal getter steps are: + + 1. Return [=this=].[=WritableStreamDefaultController/[[abortController]]=]'s + [=AbortController/signal=]. +
+ +
+ The error(|e|) method steps are: + + 1. Let |state| be [=this=].[=WritableStreamDefaultController/[[stream]]=].[=WritableStream/[[state]]=]. + 1. If |state| is not "`writable`", return. + 1. Perform ! [$WritableStreamDefaultControllerError$]([=this=], |e|). +
+ +

Internal methods

+ +The following are 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 (see +[[#rs-abstract-ops-used-by-controllers]]), where there actually are multiple controller types and +as such the counterpart internal methods are used polymorphically. + +

+ \[[AbortSteps]](|reason|) implements the + [$WritableStreamController/[[AbortSteps]]$] contract. It performs the following steps: + + 1. Let |result| be the result of performing + [=this=].[=WritableStreamDefaultController/[[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. + +
+ AcquireWritableStreamDefaultWriter(|stream|) + 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|) performs the following + steps: + + 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|.[=WritableStream/[[state]]=] to "`writable`". + 1. Set |stream|.[=WritableStream/[[storedError]]=], |stream|.[=WritableStream/[[writer]]=], + |stream|.[=WritableStream/[[controller]]=], + |stream|.[=WritableStream/[[inFlightWriteRequest]]=], + |stream|.[=WritableStream/[[closeRequest]]=], + |stream|.[=WritableStream/[[inFlightCloseRequest]]=], and + |stream|.[=WritableStream/[[pendingAbortRequest]]=] to undefined. + 1. Set |stream|.[=WritableStream/[[writeRequests]]=] to a new empty [=list=]. + 1. Set |stream|.[=WritableStream/[[backpressure]]=] to false. +
+ +
+ IsWritableStreamLocked(|stream|) performs the following steps: + + 1. If |stream|.[=WritableStream/[[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|.[=WritableStreamDefaultWriter/[[stream]]=] to |stream|. + 1. Set |stream|.[=WritableStream/[[writer]]=] to |writer|. + 1. Let |state| be |stream|.[=WritableStream/[[state]]=]. + 1. If |state| is "`writable`", + 1. If ! [$WritableStreamCloseQueuedOrInFlight$](|stream|) is false and + |stream|.[=WritableStream/[[backpressure]]=] is true, set + |writer|.[=WritableStreamDefaultWriter/[[readyPromise]]=] to [=a new promise=]. + 1. Otherwise, set |writer|.[=WritableStreamDefaultWriter/[[readyPromise]]=] to [=a promise + resolved with=] undefined. + 1. Set |writer|.[=WritableStreamDefaultWriter/[[closedPromise]]=] to [=a new promise=]. + 1. Otherwise, if |state| is "`erroring`", + 1. Set |writer|.[=WritableStreamDefaultWriter/[[readyPromise]]=] to [=a promise rejected with=] + |stream|.[=WritableStream/[[storedError]]=]. + 1. Set |writer|.[=WritableStreamDefaultWriter/[[readyPromise]]=].\[[PromiseIsHandled]] to true. + 1. Set |writer|.[=WritableStreamDefaultWriter/[[closedPromise]]=] to [=a new promise=]. + 1. Otherwise, if |state| is "`closed`", + 1. Set |writer|.[=WritableStreamDefaultWriter/[[readyPromise]]=] to [=a promise resolved with=] + undefined. + 1. Set |writer|.[=WritableStreamDefaultWriter/[[closedPromise]]=] to [=a promise resolved with=] + undefined. + 1. Otherwise, + 1. Assert: |state| is "`errored`". + 1. Let |storedError| be |stream|.[=WritableStream/[[storedError]]=]. + 1. Set |writer|.[=WritableStreamDefaultWriter/[[readyPromise]]=] to [=a promise rejected with=] + |storedError|. + 1. Set |writer|.[=WritableStreamDefaultWriter/[[readyPromise]]=].\[[PromiseIsHandled]] to true. + 1. Set |writer|.[=WritableStreamDefaultWriter/[[closedPromise]]=] to [=a promise rejected with=] + |storedError|. + 1. Set |writer|.[=WritableStreamDefaultWriter/[[closedPromise]]=].\[[PromiseIsHandled]] to true. +
+ +
+ WritableStreamAbort(|stream|, |reason|) performs the following + steps: + + 1. If |stream|.[=WritableStream/[[state]]=] is "`closed`" or "`errored`", return + [=a promise resolved with=] undefined. + 1. [=AbortController/Signal abort=] on + |stream|.[=WritableStream/[[controller]]=].[=WritableStreamDefaultController/[[abortController]]=] + with |reason|. + 1. Let |state| be |stream|.[=WritableStream/[[state]]=]. + 1. If |state| is "`closed`" or "`errored`", return [=a promise resolved with=] undefined. +

We re-check the state because [=AbortController/signaling abort=] runs author + code and that might have changed the state. + 1. If |stream|.[=WritableStream/[[pendingAbortRequest]]=] is not undefined, return + |stream|.[=WritableStream/[[pendingAbortRequest]]=]'s [=pending abort request/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|.[=WritableStream/[[pendingAbortRequest]]=] to a new [=pending abort request=] whose + [=pending abort request/promise=] is |promise|, [=pending abort request/reason=] is |reason|, + and [=pending abort request/was already erroring=] is |wasAlreadyErroring|. + 1. If |wasAlreadyErroring| is false, perform ! [$WritableStreamStartErroring$](|stream|, |reason|). + 1. Return |promise|. +

+ +
+ WritableStreamClose(|stream|) performs the following steps: + + 1. Let |state| be |stream|.[=WritableStream/[[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|.[=WritableStream/[[closeRequest]]=] to |promise|. + 1. Let |writer| be |stream|.[=WritableStream/[[writer]]=]. + 1. If |writer| is not undefined, and |stream|.[=WritableStream/[[backpressure]]=] is true, and + |state| is "`writable`", [=resolve=] |writer|.[=WritableStreamDefaultWriter/[[readyPromise]]=] + with undefined. + 1. Perform ! [$WritableStreamDefaultControllerClose$](|stream|.[=WritableStream/[[controller]]=]). + 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|.[=WritableStream/[[state]]=] is "`writable`". + 1. Let |promise| be [=a new promise=]. + 1. [=list/Append=] |promise| to |stream|.[=WritableStream/[[writeRequests]]=]. + 1. Return |promise|. +
+ +
+ WritableStreamCloseQueuedOrInFlight(|stream|) + performs the following steps: + + 1. If |stream|.[=WritableStream/[[closeRequest]]=] is undefined and + |stream|.[=WritableStream/[[inFlightCloseRequest]]=] is undefined, return false. + 1. Return true. +
+ +
+ WritableStreamDealWithRejection(|stream|, |error|) + performs the following steps: + + 1. Let |state| be |stream|.[=WritableStream/[[state]]=]. + 1. If |state| is "`writable`", + 1. Perform ! [$WritableStreamStartErroring$](|stream|, |error|). + 1. Return. + 1. Assert: |state| is "`erroring`". + 1. Perform ! [$WritableStreamFinishErroring$](|stream|). +
+ +
+ WritableStreamFinishErroring(|stream|) + performs the following steps: + + 1. Assert: |stream|.[=WritableStream/[[state]]=] is "`erroring`". + 1. Assert: ! [$WritableStreamHasOperationMarkedInFlight$](|stream|) is false. + 1. Set |stream|.[=WritableStream/[[state]]=] to "`errored`". + 1. Perform ! + |stream|.[=WritableStream/[[controller]]=].[$WritableStreamController/[[ErrorSteps]]$](). + 1. Let |storedError| be |stream|.[=WritableStream/[[storedError]]=]. + 1. [=list/For each=] |writeRequest| of |stream|.[=WritableStream/[[writeRequests]]=]: + 1. [=Reject=] |writeRequest| with |storedError|. + 1. Set |stream|.[=WritableStream/[[writeRequests]]=] to an empty [=list=]. + 1. If |stream|.[=WritableStream/[[pendingAbortRequest]]=] is undefined, + 1. Perform ! [$WritableStreamRejectCloseAndClosedPromiseIfNeeded$](|stream|). + 1. Return. + 1. Let |abortRequest| be |stream|.[=WritableStream/[[pendingAbortRequest]]=]. + 1. Set |stream|.[=WritableStream/[[pendingAbortRequest]]=] to undefined. + 1. If |abortRequest|'s [=pending abort request/was already erroring=] is true, + 1. [=Reject=] |abortRequest|'s [=pending abort request/promise=] with |storedError|. + 1. Perform ! [$WritableStreamRejectCloseAndClosedPromiseIfNeeded$](|stream|). + 1. Return. + 1. Let |promise| be ! + |stream|.[=WritableStream/[[controller]]=].[$WritableStreamController/[[AbortSteps]]$](|abortRequest|'s + [=pending abort request/reason=]). + 1. [=Upon fulfillment=] of |promise|, + 1. [=Resolve=] |abortRequest|'s [=pending abort request/promise=] with undefined. + 1. Perform ! [$WritableStreamRejectCloseAndClosedPromiseIfNeeded$](|stream|). + 1. [=Upon rejection=] of |promise| with reason |reason|, + 1. [=Reject=] |abortRequest|'s [=pending abort request/promise=] with |reason|. + 1. Perform ! [$WritableStreamRejectCloseAndClosedPromiseIfNeeded$](|stream|). +
+ +
+ WritableStreamFinishInFlightClose(|stream|) + performs the following steps: + + 1. Assert: |stream|.[=WritableStream/[[inFlightCloseRequest]]=] is not undefined. + 1. [=Resolve=] |stream|.[=WritableStream/[[inFlightCloseRequest]]=] with undefined. + 1. Set |stream|.[=WritableStream/[[inFlightCloseRequest]]=] to undefined. + 1. Let |state| be |stream|.[=WritableStream/[[state]]=]. + 1. Assert: |stream|.[=WritableStream/[[state]]=] is "`writable`" or "`erroring`". + 1. If |state| is "`erroring`", + 1. Set |stream|.[=WritableStream/[[storedError]]=] to undefined. + 1. If |stream|.[=WritableStream/[[pendingAbortRequest]]=] is not undefined, + 1. [=Resolve=] |stream|.[=WritableStream/[[pendingAbortRequest]]=]'s [=pending abort + request/promise=] with undefined. + 1. Set |stream|.[=WritableStream/[[pendingAbortRequest]]=] to undefined. + 1. Set |stream|.[=WritableStream/[[state]]=] to "`closed`". + 1. Let |writer| be |stream|.[=WritableStream/[[writer]]=]. + 1. If |writer| is not undefined, [=resolve=] + |writer|.[=WritableStreamDefaultWriter/[[closedPromise]]=] with undefined. + 1. Assert: |stream|.[=WritableStream/[[pendingAbortRequest]]=] is undefined. + 1. Assert: |stream|.[=WritableStream/[[storedError]]=] is undefined. +
+ +
+ WritableStreamFinishInFlightCloseWithError(|stream|, + |error|) performs the following steps: + + 1. Assert: |stream|.[=WritableStream/[[inFlightCloseRequest]]=] is not undefined. + 1. [=Reject=] |stream|.[=WritableStream/[[inFlightCloseRequest]]=] with |error|. + 1. Set |stream|.[=WritableStream/[[inFlightCloseRequest]]=] to undefined. + 1. Assert: |stream|.[=WritableStream/[[state]]=] is "`writable`" or "`erroring`". + 1. If |stream|.[=WritableStream/[[pendingAbortRequest]]=] is not undefined, + 1. [=Reject=] |stream|.[=WritableStream/[[pendingAbortRequest]]=]'s [=pending abort + request/promise=] with |error|. + 1. Set |stream|.[=WritableStream/[[pendingAbortRequest]]=] to undefined. + 1. Perform ! [$WritableStreamDealWithRejection$](|stream|, |error|). +
+ +
+ WritableStreamFinishInFlightWrite(|stream|) + performs the following steps: + + 1. Assert: |stream|.[=WritableStream/[[inFlightWriteRequest]]=] is not undefined. + 1. [=Resolve=] |stream|.[=WritableStream/[[inFlightWriteRequest]]=] with undefined. + 1. Set |stream|.[=WritableStream/[[inFlightWriteRequest]]=] to undefined. +
+ +
+ WritableStreamFinishInFlightWriteWithError(|stream|, + |error|) performs the following steps: + + 1. Assert: |stream|.[=WritableStream/[[inFlightWriteRequest]]=] is not undefined. + 1. [=Reject=] |stream|.[=WritableStream/[[inFlightWriteRequest]]=] with |error|. + 1. Set |stream|.[=WritableStream/[[inFlightWriteRequest]]=] to undefined. + 1. Assert: |stream|.[=WritableStream/[[state]]=] is "`writable`" or "`erroring`". + 1. Perform ! [$WritableStreamDealWithRejection$](|stream|, |error|). +
+ +
+ WritableStreamHasOperationMarkedInFlight(|stream|) + performs the following steps: + + 1. If |stream|.[=WritableStream/[[inFlightWriteRequest]]=] is undefined and + |stream|.[=WritableStream/[[inFlightCloseRequest]]=] is undefined, return false. + 1. Return true. +
+ +
+ WritableStreamMarkCloseRequestInFlight(|stream|) + performs the following steps: + + 1. Assert: |stream|.[=WritableStream/[[inFlightCloseRequest]]=] is undefined. + 1. Assert: |stream|.[=WritableStream/[[closeRequest]]=] is not undefined. + 1. Set |stream|.[=WritableStream/[[inFlightCloseRequest]]=] to + |stream|.[=WritableStream/[[closeRequest]]=]. + 1. Set |stream|.[=WritableStream/[[closeRequest]]=] to undefined. +
+ +
+ WritableStreamMarkFirstWriteRequestInFlight(|stream|) + performs the following steps: + + 1. Assert: |stream|.[=WritableStream/[[inFlightWriteRequest]]=] is undefined. + 1. Assert: |stream|.[=WritableStream/[[writeRequests]]=] is not empty. + 1. Let |writeRequest| be |stream|.[=WritableStream/[[writeRequests]]=][0]. + 1. [=list/Remove=] |writeRequest| from |stream|.[=WritableStream/[[writeRequests]]=]. + 1. Set |stream|.[=WritableStream/[[inFlightWriteRequest]]=] to |writeRequest|. +
+ +
+ WritableStreamRejectCloseAndClosedPromiseIfNeeded(|stream|) + performs the following steps: + + 1. Assert: |stream|.[=WritableStream/[[state]]=] is "`errored`". + 1. If |stream|.[=WritableStream/[[closeRequest]]=] is not undefined, + 1. Assert: |stream|.[=WritableStream/[[inFlightCloseRequest]]=] is undefined. + 1. [=Reject=] |stream|.[=WritableStream/[[closeRequest]]=] with + |stream|.[=WritableStream/[[storedError]]=]. + 1. Set |stream|.[=WritableStream/[[closeRequest]]=] to undefined. + 1. Let |writer| be |stream|.[=WritableStream/[[writer]]=]. + 1. If |writer| is not undefined, + 1. [=Reject=] |writer|.[=WritableStreamDefaultWriter/[[closedPromise]]=] with + |stream|.[=WritableStream/[[storedError]]=]. + 1. Set |writer|.[=WritableStreamDefaultWriter/[[closedPromise]]=].\[[PromiseIsHandled]] to true. +
+ +
+ WritableStreamStartErroring(|stream|, |reason|) + performs the following steps: + + 1. Assert: |stream|.[=WritableStream/[[storedError]]=] is undefined. + 1. Assert: |stream|.[=WritableStream/[[state]]=] is "`writable`". + 1. Let |controller| be |stream|.[=WritableStream/[[controller]]=]. + 1. Assert: |controller| is not undefined. + 1. Set |stream|.[=WritableStream/[[state]]=] to "`erroring`". + 1. Set |stream|.[=WritableStream/[[storedError]]=] to |reason|. + 1. Let |writer| be |stream|.[=WritableStream/[[writer]]=]. + 1. If |writer| is not undefined, perform ! + [$WritableStreamDefaultWriterEnsureReadyPromiseRejected$](|writer|, |reason|). + 1. If ! [$WritableStreamHasOperationMarkedInFlight$](|stream|) is false and + |controller|.[=WritableStreamDefaultController/[[started]]=] is true, perform ! + [$WritableStreamFinishErroring$](|stream|). +
+ +
+ WritableStreamUpdateBackpressure(|stream|, + |backpressure|) performs the following steps: + + 1. Assert: |stream|.[=WritableStream/[[state]]=] is "`writable`". + 1. Assert: ! [$WritableStreamCloseQueuedOrInFlight$](|stream|) is false. + 1. Let |writer| be |stream|.[=WritableStream/[[writer]]=]. + 1. If |writer| is not undefined and |backpressure| is not + |stream|.[=WritableStream/[[backpressure]]=], + 1. If |backpressure| is true, set |writer|.[=WritableStreamDefaultWriter/[[readyPromise]]=] to + [=a new promise=]. + 1. Otherwise, + 1. Assert: |backpressure| is false. + 1. [=Resolve=] |writer|.[=WritableStreamDefaultWriter/[[readyPromise]]=] with undefined. + 1. Set |stream|.[=WritableStream/[[backpressure]]=] to |backpressure|. +
+ +

Writers

+ +The following abstract operations support the implementation and manipulation of +{{WritableStreamDefaultWriter}} instances. + +
+ WritableStreamDefaultWriterAbort(|writer|, + |reason|) performs the following steps: + + 1. Let |stream| be |writer|.[=WritableStreamDefaultWriter/[[stream]]=]. + 1. Assert: |stream| is not undefined. + 1. Return ! [$WritableStreamAbort$](|stream|, |reason|). +
+ +
+ WritableStreamDefaultWriterClose(|writer|) performs + the following steps: + + 1. Let |stream| be |writer|.[=WritableStreamDefaultWriter/[[stream]]=]. + 1. Assert: |stream| is not undefined. + 1. Return ! [$WritableStreamClose$](|stream|). +
+ +
+ WritableStreamDefaultWriterCloseWithErrorPropagation(|writer|) + performs the following steps: + + 1. Let |stream| be |writer|.[=WritableStreamDefaultWriter/[[stream]]=]. + 1. Assert: |stream| is not undefined. + 1. Let |state| be |stream|.[=WritableStream/[[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|.[=WritableStream/[[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|.[=WritableStreamDefaultWriter/[[closedPromise]]=].\[[PromiseState]] is "`pending`", + [=reject=] |writer|.[=WritableStreamDefaultWriter/[[closedPromise]]=] with |error|. + 1. Otherwise, set |writer|.[=WritableStreamDefaultWriter/[[closedPromise]]=] to [=a promise + rejected with=] |error|. + 1. Set |writer|.[=WritableStreamDefaultWriter/[[closedPromise]]=].\[[PromiseIsHandled]] to true. +
+ +
+ WritableStreamDefaultWriterEnsureReadyPromiseRejected(|writer|, + |error|) performs the following steps: + + 1. If |writer|.[=WritableStreamDefaultWriter/[[readyPromise]]=].\[[PromiseState]] is "`pending`", + [=reject=] |writer|.[=WritableStreamDefaultWriter/[[readyPromise]]=] with |error|. + 1. Otherwise, set |writer|.[=WritableStreamDefaultWriter/[[readyPromise]]=] to [=a promise rejected + with=] |error|. + 1. Set |writer|.[=WritableStreamDefaultWriter/[[readyPromise]]=].\[[PromiseIsHandled]] to true. +
+ +
+ WritableStreamDefaultWriterGetDesiredSize(|writer|) + performs the following steps: + + 1. Let |stream| be |writer|.[=WritableStreamDefaultWriter/[[stream]]=]. + 1. Let |state| be |stream|.[=WritableStream/[[state]]=]. + 1. If |state| is "`errored`" or "`erroring`", return null. + 1. If |state| is "`closed`", return 0. + 1. Return ! + [$WritableStreamDefaultControllerGetDesiredSize$](|stream|.[=WritableStream/[[controller]]=]). +
+ +
+ WritableStreamDefaultWriterRelease(|writer|) + performs the following steps: + + 1. Let |stream| be |writer|.[=WritableStreamDefaultWriter/[[stream]]=]. + 1. Assert: |stream| is not undefined. + 1. Assert: |stream|.[=WritableStream/[[writer]]=] is |writer|. + 1. Let |releasedError| be a new {{TypeError}}. + 1. Perform ! [$WritableStreamDefaultWriterEnsureReadyPromiseRejected$](|writer|, |releasedError|). + 1. Perform ! [$WritableStreamDefaultWriterEnsureClosedPromiseRejected$](|writer|, |releasedError|). + 1. Set |stream|.[=WritableStream/[[writer]]=] to undefined. + 1. Set |writer|.[=WritableStreamDefaultWriter/[[stream]]=] to undefined. +
+ +
+ WritableStreamDefaultWriterWrite(|writer|, |chunk|) + performs the following steps: + + 1. Let |stream| be |writer|.[=WritableStreamDefaultWriter/[[stream]]=]. + 1. Assert: |stream| is not undefined. + 1. Let |controller| be |stream|.[=WritableStream/[[controller]]=]. + 1. Let |chunkSize| be ! [$WritableStreamDefaultControllerGetChunkSize$](|controller|, |chunk|). + 1. If |stream| is not equal to |writer|.[=WritableStreamDefaultWriter/[[stream]]=], return [=a + promise rejected with=] a {{TypeError}} exception. + 1. Let |state| be |stream|.[=WritableStream/[[state]]=]. + 1. If |state| is "`errored`", return [=a promise rejected with=] + |stream|.[=WritableStream/[[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|.[=WritableStream/[[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|.[=WritableStream/[[controller]]=] is undefined. + 1. Set |controller|.[=WritableStreamDefaultController/[[stream]]=] to |stream|. + 1. Set |stream|.[=WritableStream/[[controller]]=] to |controller|. + 1. Perform ! [$ResetQueue$](|controller|). + 1. Set |controller|.[=WritableStreamDefaultController/[[abortController]]=] to a new + {{AbortController}}. + 1. Set |controller|.[=WritableStreamDefaultController/[[started]]=] to false. + 1. Set |controller|.[=WritableStreamDefaultController/[[strategySizeAlgorithm]]=] to + |sizeAlgorithm|. + 1. Set |controller|.[=WritableStreamDefaultController/[[strategyHWM]]=] to |highWaterMark|. + 1. Set |controller|.[=WritableStreamDefaultController/[[writeAlgorithm]]=] to |writeAlgorithm|. + 1. Set |controller|.[=WritableStreamDefaultController/[[closeAlgorithm]]=] to |closeAlgorithm|. + 1. Set |controller|.[=WritableStreamDefaultController/[[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|.[=WritableStream/[[state]]=] is "`writable`" or "`erroring`". + 1. Set |controller|.[=WritableStreamDefaultController/[[started]]=] to true. + 1. Perform ! [$WritableStreamDefaultControllerAdvanceQueueIfNeeded$](|controller|). + 1. [=Upon rejection=] of |startPromise| with reason |r|, + 1. Assert: |stream|.[=WritableStream/[[state]]=] is "`writable`" or "`erroring`". + 1. Set |controller|.[=WritableStreamDefaultController/[[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|.[=WritableStreamDefaultController/[[stream]]=]. + 1. If |controller|.[=WritableStreamDefaultController/[[started]]=] is false, return. + 1. If |stream|.[=WritableStream/[[inFlightWriteRequest]]=] is not undefined, return. + 1. Let |state| be |stream|.[=WritableStream/[[state]]=]. + 1. Assert: |state| is not "`closed`" or "`errored`". + 1. If |state| is "`erroring`", + 1. Perform ! [$WritableStreamFinishErroring$](|stream|). + 1. Return. + 1. If |controller|.[=WritableStreamDefaultController/[[queue]]=] is empty, return. + 1. Let |value| be ! [$PeekQueueValue$](|controller|). + 1. If |value| is the [=close sentinel=], perform ! + [$WritableStreamDefaultControllerProcessClose$](|controller|). + 1. Otherwise, perform ! [$WritableStreamDefaultControllerProcessWrite$](|controller|, + |value|). +
+ +
+ 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|.[=WritableStreamDefaultController/[[writeAlgorithm]]=] to undefined. + 1. Set |controller|.[=WritableStreamDefaultController/[[closeAlgorithm]]=] to undefined. + 1. Set |controller|.[=WritableStreamDefaultController/[[abortAlgorithm]]=] to undefined. + 1. Set |controller|.[=WritableStreamDefaultController/[[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 sentinel=], 0). + 1. Perform ! [$WritableStreamDefaultControllerAdvanceQueueIfNeeded$](|controller|). +
+ +
+ WritableStreamDefaultControllerError(|controller|, + |error|) performs the following steps: + + 1. Let |stream| be |controller|.[=WritableStreamDefaultController/[[stream]]=]. + 1. Assert: |stream|.[=WritableStream/[[state]]=] is "`writable`". + 1. Perform ! [$WritableStreamDefaultControllerClearAlgorithms$](|controller|). + 1. Perform ! [$WritableStreamStartErroring$](|stream|, |error|). +
+ +
+ WritableStreamDefaultControllerErrorIfNeeded(|controller|, + |error|) performs the following steps: + + 1. If |controller|.[=WritableStreamDefaultController/[[stream]]=].[=WritableStream/[[state]]=] is + "`writable`", perform ! [$WritableStreamDefaultControllerError$](|controller|, |error|). +
+ +
+ WritableStreamDefaultControllerGetBackpressure(|controller|) + performs the following steps: + + 1. Let |desiredSize| be ! [$WritableStreamDefaultControllerGetDesiredSize$](|controller|). + 1. Return true if |desiredSize| ≤ 0, or false otherwise. +
+ +
+ WritableStreamDefaultControllerGetChunkSize(|controller|, + |chunk|) performs the following steps: + + 1. Let |returnValue| be the result of performing + |controller|.[=WritableStreamDefaultController/[[strategySizeAlgorithm]]=], passing in |chunk|, + and interpreting the result as a [=completion record=]. + 1. If |returnValue| is an abrupt completion, + 1. Perform ! [$WritableStreamDefaultControllerErrorIfNeeded$](|controller|, + |returnValue|.\[[Value]]). + 1. Return 1. + 1. Return |returnValue|.\[[Value]]. +
+ +
+ WritableStreamDefaultControllerGetDesiredSize(|controller|) + performs the following steps: + + 1. Return |controller|.[=WritableStreamDefaultController/[[strategyHWM]]=] − + |controller|.[=WritableStreamDefaultController/[[queueTotalSize]]=]. +
+ +
+ WritableStreamDefaultControllerProcessClose(|controller|) + performs the following steps: + + 1. Let |stream| be |controller|.[=WritableStreamDefaultController/[[stream]]=]. + 1. Perform ! [$WritableStreamMarkCloseRequestInFlight$](|stream|). + 1. Perform ! [$DequeueValue$](|controller|). + 1. Assert: |controller|.[=WritableStreamDefaultController/[[queue]]=] is empty. + 1. Let |sinkClosePromise| be the result of performing + |controller|.[=WritableStreamDefaultController/[[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|.[=WritableStreamDefaultController/[[stream]]=]. + 1. Perform ! [$WritableStreamMarkFirstWriteRequestInFlight$](|stream|). + 1. Let |sinkWritePromise| be the result of performing + |controller|.[=WritableStreamDefaultController/[[writeAlgorithm]]=], passing in |chunk|. + 1. [=Upon fulfillment=] of |sinkWritePromise|, + 1. Perform ! [$WritableStreamFinishInFlightWrite$](|stream|). + 1. Let |state| be |stream|.[=WritableStream/[[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|.[=WritableStream/[[state]]=] is "`writable`", perform ! + [$WritableStreamDefaultControllerClearAlgorithms$](|controller|). + 1. Perform ! [$WritableStreamFinishInFlightWriteWithError$](|stream|, |reason|). +
+ +
+ WritableStreamDefaultControllerWrite(|controller|, + |chunk|, |chunkSize|) performs the following steps: + + 1. Let |enqueueResult| be [$EnqueueValueWithSize$](|controller|, |chunk|, |chunkSize|). + 1. If |enqueueResult| is an abrupt completion, + 1. Perform ! [$WritableStreamDefaultControllerErrorIfNeeded$](|controller|, + |enqueueResult|.\[[Value]]). + 1. Return. + 1. Let |stream| be |controller|.[=WritableStreamDefaultController/[[stream]]=]. + 1. If ! [$WritableStreamCloseQueuedOrInFlight$](|stream|) is false and + |stream|.[=WritableStream/[[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 [=piping|pipe=] between a [=readable + stream=] and a [=writable stream=]. [=Chunks=] that travel from the [=readable stream=] to the + [=writable stream=] will be transformed as they pass through the transform stream. + [=Backpressure=] is respected, so data will not be read faster than it can be transformed and + consumed. + + + readableStream + .pipeThrough(transformStream) + .pipeTo(writableStream) + .then(() => console.log("All data successfully transformed!")) + .catch(e => console.error("Something went wrong!", e)); + +
+ +
+ You can also use the {{TransformStream/readable}} and {{TransformStream/writable}} properties of a + transform stream directly to access the usual interfaces of a [=readable stream=] and [=writable + stream=]. In this example we supply data to the [=writable side=] of the stream using its + [=writer=] interface. The [=readable side=] is then piped to + anotherWritableStream. + + + const writer = transformStream.writable.getWriter(); + writer.write("input chunk"); + transformStream.readable.pipeTo(anotherWritableStream); + +
+ +
+ One use of [=identity transform streams=] is to easily convert between readable and writable + streams. For example, the {{fetch(input)|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=*, Transferable] +interface TransformStream { + constructor(optional object transformer, + optional QueuingStrategy writableStrategy = {}, + optional QueuingStrategy readableStrategy = {}); + + readonly attribute ReadableStream readable; + readonly attribute WritableStream writable; +}; + + +

Internal slots

+ +Instances of {{TransformStream}} are created with the internal slots described in the following +table: + + + + + + + + + + + + + +
Internal SlotDescription (non-normative)
\[[backpressure]] + Whether there was backpressure on [=TransformStream/[[readable]]=] the + last time it was observed +
\[[backpressureChangePromise]] + A promise which is fulfilled and replaced every time the value of + [=TransformStream/[[backpressure]]=] changes +
\[[controller]] + A {{TransformStreamDefaultController}} created with the ability to + control [=TransformStream/[[readable]]=] and [=TransformStream/[[writable]]=] +
\[[Detached]] + A boolean flag set to true when the stream is transferred +
\[[readable]] + The {{ReadableStream}} instance controlled by this object +
\[[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 { + TransformerStartCallback start; + TransformerTransformCallback transform; + TransformerFlushCallback flush; + TransformerCancelCallback cancel; + any readableType; + any writableType; +}; + +callback TransformerStartCallback = any (TransformStreamDefaultController controller); +callback TransformerFlushCallback = Promise<undefined> (TransformStreamDefaultController controller); +callback TransformerTransformCallback = Promise<undefined> (any chunk, TransformStreamDefaultController controller); +callback TransformerCancelCallback = Promise<undefined> (any reason); + + +
+
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. + +

The promise potentially returned by this function is used to ensure that well-behaved [=producers=] do not attempt to mutate the [=chunk=] + before it has been fully transformed. (This is not guaranteed by any specification machinery, but + instead is an informal contract between [=producers=] and the [=transformer=].) + +

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

cancel(reason)
+
+

A function called when the [=readable side=] is cancelled, or when the [=writable side=] is + aborted. + +

Typically this is used to clean up underlying transformer resources when the stream is aborted + or cancelled. + +

If the cancellation process is asynchronous, the function can return a promise to signal + success or failure; the result will be communicated to the caller of + {{WritableStream/abort()|stream.writable.abort()}} or + {{ReadableStream/cancel()|stream.readable.cancel()}}. 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/cancel|cancel()}}; the stream is already in the process of cancelling/aborting, 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. + +

writableType
+
+

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

+ +The controller object passed to {{Transformer/start|start()}}, +{{Transformer/transform|transform()}}, and {{Transformer/flush|flush()}} is an instance of +{{TransformStreamDefaultController}}, and has the ability to enqueue [=chunks=] to the +[=readable side=], or to terminate or error the stream. + +

Constructor and properties

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

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

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

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

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

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

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

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

+ +
+ The new TransformStream(|transformer|, |writableStrategy|, + |readableStrategy|) constructor steps are: + + 1. If |transformer| is missing, set it to null. + 1. Let |transformerDict| be |transformer|, [=converted to an IDL value=] of type {{Transformer}}. +

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

+ +
+ The readable getter steps + are: + + 1. Return [=this=].[=TransformStream/[[readable]]=]. +
+ +
+ The writable getter steps + are: + + 1. Return [=this=].[=TransformStream/[[writable]]=]. +
+ +

Transfer via `postMessage()`

+ +
+
destination.postMessage(ts, { transfer: [ts] }); +
+

Sends a {{TransformStream}} to another frame, window, or worker. + +

The transferred stream can be used exactly like the original. Its [=readable side|readable=] + and [=writable sides=] will become locked and no longer directly usable. +

+
+ +
+ {{TransformStream}} objects are [=transferable objects=]. Their [=transfer steps=], given |value| + and |dataHolder|, are: + + 1. Let |readable| be |value|.[=TransformStream/[[readable]]=]. + 1. Let |writable| be |value|.[=TransformStream/[[writable]]=]. + 1. If ! [$IsReadableStreamLocked$](|readable|) is true, throw a "{{DataCloneError}}" + {{DOMException}}. + 1. If ! [$IsWritableStreamLocked$](|writable|) is true, throw a "{{DataCloneError}}" + {{DOMException}}. + 1. Set |dataHolder|.\[[readable]] to ! [$StructuredSerializeWithTransfer$](|readable|, + « |readable| »). + 1. Set |dataHolder|.\[[writable]] to ! [$StructuredSerializeWithTransfer$](|writable|, + « |writable| »). +
+ +
+ Their [=transfer-receiving steps=], given |dataHolder| and |value|, are: + + 1. Let |readableRecord| be ! [$StructuredDeserializeWithTransfer$](|dataHolder|.\[[readable]], + [=the current Realm=]). + 1. Let |writableRecord| be ! [$StructuredDeserializeWithTransfer$](|dataHolder|.\[[writable]], + [=the current Realm=]). + 1. Set |value|.[=TransformStream/[[readable]]=] to |readableRecord|.\[[Deserialized]]. + 1. Set |value|.[=TransformStream/[[writable]]=] to |writableRecord|.\[[Deserialized]]. + 1. Set |value|.[=TransformStream/[[backpressure]]=], + |value|.[=TransformStream/[[backpressureChangePromise]]=], and + |value|.[=TransformStream/[[controller]]=] to undefined. + +

The [=TransformStream/[[backpressure]]=], + [=TransformStream/[[backpressureChangePromise]]=], and [=TransformStream/[[controller]]=] slots are + not used in a transferred {{TransformStream}}.

+
+ +

The {{TransformStreamDefaultController}} class

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

Interface definition

+ +The Web IDL definition for the {{TransformStreamDefaultController}} class is given as follows: + + +[Exposed=*] +interface TransformStreamDefaultController { + readonly attribute unrestricted double? desiredSize; + + undefined enqueue(optional any chunk); + undefined error(optional any reason); + undefined terminate(); +}; + + +

Internal slots

+ +Instances of {{TransformStreamDefaultController}} are created with the internal slots described in +the following table: + + + + + + + + + + + + +
Internal SlotDescription (non-normative)
\[[cancelAlgorithm]] + A promise-returning algorithm, taking one argument (the reason for + cancellation), which communicates a requested cancellation to the [=transformer=] +
\[[finishPromise]] + A promise which resolves on completion of either the + [=TransformStreamDefaultController/[[cancelAlgorithm]]=] or the + [=TransformStreamDefaultController/[[flushAlgorithm]]=]. If this field is unpopulated (that is, + undefined), then neither of those algorithms have been [=invoked=] yet +
\[[flushAlgorithm]] + A promise-returning algorithm which communicates a requested close to + the [=transformer=] +
\[[stream]] + The {{TransformStream}} instance controlled +
\[[transformAlgorithm]] + A promise-returning algorithm, taking one argument (the [=chunk=] to + transform), which requests the [=transformer=] perform its transformation +
+ +

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 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 desiredSize getter steps are: + + 1. Let |readableController| be [=this=].[=TransformStreamDefaultController/[[stream]]=].[=TransformStream/[[readable]]=].[=ReadableStream/[[controller]]=]. + 1. Return ! [$ReadableStreamDefaultControllerGetDesiredSize$](|readableController|). +
+ +
+ The enqueue(|chunk|) method steps are: + + 1. Perform ? [$TransformStreamDefaultControllerEnqueue$]([=this=], |chunk|). +
+ +
+ The error(|e|) method steps are: + + 1. Perform ? [$TransformStreamDefaultControllerError$]([=this=], |e|). +
+ +
+ The terminate() method steps are: + + 1. Perform ? [$TransformStreamDefaultControllerTerminate$]([=this=]). +
+ +

Abstract operations

+ +

Working with transform streams

+ +The following abstract operations operate on {{TransformStream}} instances at a higher level. + +
+ 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|.[=TransformStream/[[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. Return ! [$TransformStreamDefaultSourceCancelAlgorithm$](|stream|, |reason|). + 1. Set |stream|.[=TransformStream/[[readable]]=] to ! [$CreateReadableStream$](|startAlgorithm|, + |pullAlgorithm|, |cancelAlgorithm|, |readableHighWaterMark|, |readableSizeAlgorithm|). + 1. Set |stream|.[=TransformStream/[[backpressure]]=] and + |stream|.[=TransformStream/[[backpressureChangePromise]]=] to undefined. +

The [=TransformStream/[[backpressure]]=] slot is set to undefined so that it can + be initialized by [$TransformStreamSetBackpressure$]. Alternatively, implementations can use a + strictly boolean value for [=TransformStream/[[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|.[=TransformStream/[[controller]]=] to undefined. +

+ +
+ TransformStreamError(|stream|, |e|) performs the following steps: + + 1. Perform ! [$ReadableStreamDefaultControllerError$](|stream|.[=TransformStream/[[readable]]=].[=ReadableStream/[[controller]]=], |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|) performs the following steps: + + 1. Perform ! [$TransformStreamDefaultControllerClearAlgorithms$](|stream|.[=TransformStream/[[controller]]=]). + 1. Perform ! + [$WritableStreamDefaultControllerErrorIfNeeded$](|stream|.[=TransformStream/[[writable]]=].[=WritableStream/[[controller]]=], |e|). + 1. Perform ! [$TransformStreamUnblockWrite$](|stream|). +
+ +
+ TransformStreamSetBackpressure(|stream|, + |backpressure|) performs the following steps: + + 1. Assert: |stream|.[=TransformStream/[[backpressure]]=] is not |backpressure|. + 1. If |stream|.[=TransformStream/[[backpressureChangePromise]]=] is not undefined, [=resolve=] + stream.[=TransformStream/[[backpressureChangePromise]]=] with undefined. + 1. Set |stream|.[=TransformStream/[[backpressureChangePromise]]=] to [=a new promise=]. + 1. Set |stream|.[=TransformStream/[[backpressure]]=] to |backpressure|. +
+ +
+ TransformStreamUnblockWrite(|stream|) performs the + following steps: + + 1. If |stream|.[=TransformStream/[[backpressure]]=] is true, perform ! [$TransformStreamSetBackpressure$](|stream|, + false). + +

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

+ +

Default controllers

+ +The following abstract operations support the implementaiton of the +{{TransformStreamDefaultController}} class. + +
+ SetUpTransformStreamDefaultController(|stream|, + |controller|, |transformAlgorithm|, |flushAlgorithm|, |cancelAlgorithm|) performs the + following steps: + + 1. Assert: |stream| [=implements=] {{TransformStream}}. + 1. Assert: |stream|.[=TransformStream/[[controller]]=] is undefined. + 1. Set |controller|.[=TransformStreamDefaultController/[[stream]]=] to |stream|. + 1. Set |stream|.[=TransformStream/[[controller]]=] to |controller|. + 1. Set |controller|.[=TransformStreamDefaultController/[[transformAlgorithm]]=] to + |transformAlgorithm|. + 1. Set |controller|.[=TransformStreamDefaultController/[[flushAlgorithm]]=] to |flushAlgorithm|. + 1. Set |controller|.[=TransformStreamDefaultController/[[cancelAlgorithm]]=] to |cancelAlgorithm|. +
+ +
+ 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. Let |cancelAlgorithm| 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. If |transformerDict|["{{Transformer/cancel}}"] [=map/exists=], set |cancelAlgorithm| to an + algorithm which takes an argument |reason| and returns the result of [=invoking=] + |transformerDict|["{{Transformer/cancel}}"] with argument list « |reason| » and + [=callback this value=] |transformer|. + 1. Perform ! [$SetUpTransformStreamDefaultController$](|stream|, |controller|, + |transformAlgorithm|, |flushAlgorithm|, |cancelAlgorithm|). +
+ +
+ 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. + +

This is observable using weak + references. See tc39/proposal-weakrefs#31 for more + detail. + + It performs the following steps: + + 1. Set |controller|.[=TransformStreamDefaultController/[[transformAlgorithm]]=] to undefined. + 1. Set |controller|.[=TransformStreamDefaultController/[[flushAlgorithm]]=] to undefined. + 1. Set |controller|.[=TransformStreamDefaultController/[[cancelAlgorithm]]=] to undefined. +

+ +
+ TransformStreamDefaultControllerEnqueue(|controller|, + |chunk|) performs the following steps: + + 1. Let |stream| be |controller|.[=TransformStreamDefaultController/[[stream]]=]. + 1. Let |readableController| be + |stream|.[=TransformStream/[[readable]]=].[=ReadableStream/[[controller]]=]. + 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|.[=TransformStream/[[readable]]=].[=ReadableStream/[[storedError]]=]. + 1. Let |backpressure| be ! + [$ReadableStreamDefaultControllerHasBackpressure$](|readableController|). + 1. If |backpressure| is not |stream|.[=TransformStream/[[backpressure]]=], + 1. Assert: |backpressure| is true. + 1. Perform ! [$TransformStreamSetBackpressure$](|stream|, true). +
+
+ TransformStreamDefaultControllerError(|controller|, + |e|) performs the following steps: + + 1. Perform ! [$TransformStreamError$](|controller|.[=TransformStreamDefaultController/[[stream]]=], + |e|). +
+ +
+ TransformStreamDefaultControllerPerformTransform(|controller|, + |chunk|) performs the following steps: + + 1. Let |transformPromise| be the result of performing + |controller|.[=TransformStreamDefaultController/[[transformAlgorithm]]=], passing |chunk|. + 1. Return the result of [=reacting=] to |transformPromise| with the following + rejection steps given the argument |r|: + 1. Perform ! + [$TransformStreamError$](|controller|.[=TransformStreamDefaultController/[[stream]]=], |r|). + 1. Throw |r|. +
+ +
+ TransformStreamDefaultControllerTerminate(|controller|) + performs the following steps: + + 1. Let |stream| be |controller|.[=TransformStreamDefaultController/[[stream]]=]. + 1. Let |readableController| be + |stream|.[=TransformStream/[[readable]]=].[=ReadableStream/[[controller]]=]. + 1. Perform ! [$ReadableStreamDefaultControllerClose$](|readableController|). + 1. Let |error| be a {{TypeError}} exception indicating that the stream has been terminated. + 1. Perform ! [$TransformStreamErrorWritableAndUnblockWrite$](|stream|, |error|). +
+ +

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|.[=TransformStream/[[writable]]=].[=WritableStream/[[state]]=] is "`writable`". + 1. Let |controller| be |stream|.[=TransformStream/[[controller]]=]. + 1. If |stream|.[=TransformStream/[[backpressure]]=] is true, + 1. Let |backpressureChangePromise| be |stream|.[=TransformStream/[[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|.[=TransformStream/[[writable]]=]. + 1. Let |state| be |writable|.[=WritableStream/[[state]]=]. + 1. If |state| is "`erroring`", throw |writable|.[=WritableStream/[[storedError]]=]. + 1. Assert: |state| is "`writable`". + 1. Return ! [$TransformStreamDefaultControllerPerformTransform$](|controller|, |chunk|). + 1. Return ! [$TransformStreamDefaultControllerPerformTransform$](|controller|, |chunk|). +
+ +
+ TransformStreamDefaultSinkAbortAlgorithm(|stream|, + |reason|) performs the following steps: + + 1. Let |controller| be |stream|.[=TransformStream/[[controller]]=]. + 1. If |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] is not undefined, return + |controller|.[=TransformStreamDefaultController/[[finishPromise]]=]. + 1. Let |readable| be |stream|.[=TransformStream/[[readable]]=]. + 1. Let |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] be a new promise. + 1. Let |cancelPromise| be the result of performing + |controller|.[=TransformStreamDefaultController/[[cancelAlgorithm]]=], passing |reason|. + 1. Perform ! [$TransformStreamDefaultControllerClearAlgorithms$](|controller|). + 1. [=React=] to |cancelPromise|: + 1. If |cancelPromise| was fulfilled, then: + 1. If |readable|.[=ReadableStream/[[state]]=] is "`errored`", [=reject=] + |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] with + |readable|.[=ReadableStream/[[storedError]]=]. + 1. Otherwise: + 1. Perform ! [$ReadableStreamDefaultControllerError$](|readable|.[=ReadableStream/[[controller]]=], |reason|). + 1. [=Resolve=] |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] with undefined. + 1. If |cancelPromise| was rejected with reason |r|, then: + 1. Perform ! [$ReadableStreamDefaultControllerError$](|readable|.[=ReadableStream/[[controller]]=], |r|). + 1. [=Reject=] |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] with |r|. + 1. Return |controller|.[=TransformStreamDefaultController/[[finishPromise]]=]. +
+ +
+ TransformStreamDefaultSinkCloseAlgorithm(|stream|) + performs the following steps: + + 1. Let |controller| be |stream|.[=TransformStream/[[controller]]=]. + 1. If |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] is not undefined, return + |controller|.[=TransformStreamDefaultController/[[finishPromise]]=]. + 1. Let |readable| be |stream|.[=TransformStream/[[readable]]=]. + 1. Let |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] be a new promise. + 1. Let |flushPromise| be the result of performing + |controller|.[=TransformStreamDefaultController/[[flushAlgorithm]]=]. + 1. Perform ! [$TransformStreamDefaultControllerClearAlgorithms$](|controller|). + 1. [=React=] to |flushPromise|: + 1. If |flushPromise| was fulfilled, then: + 1. If |readable|.[=ReadableStream/[[state]]=] is "`errored`", [=reject=] + |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] with + |readable|.[=ReadableStream/[[storedError]]=]. + 1. Otherwise: + 1. Perform ! [$ReadableStreamDefaultControllerClose$](|readable|.[=ReadableStream/[[controller]]=]). + 1. [=Resolve=] |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] with undefined. + 1. If |flushPromise| was rejected with reason |r|, then: + 1. Perform ! [$ReadableStreamDefaultControllerError$](|readable|.[=ReadableStream/[[controller]]=], |r|). + 1. [=Reject=] |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] with |r|. + 1. Return |controller|.[=TransformStreamDefaultController/[[finishPromise]]=]. +
+ +

Default sources

+ +The following abstract operation is used to implement the [=underlying source=] for the [=readable +side=] of [=transform streams=]. + +
+ TransformStreamDefaultSourceCancelAlgorithm(|stream|, + |reason|) performs the following steps: + + 1. Let |controller| be |stream|.[=TransformStream/[[controller]]=]. + 1. If |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] is not undefined, return + |controller|.[=TransformStreamDefaultController/[[finishPromise]]=]. + 1. Let |writable| be |stream|.[=TransformStream/[[writable]]=]. + 1. Let |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] be a new promise. + 1. Let |cancelPromise| be the result of performing + |controller|.[=TransformStreamDefaultController/[[cancelAlgorithm]]=], passing |reason|. + 1. Perform ! [$TransformStreamDefaultControllerClearAlgorithms$](|controller|). + 1. [=React=] to |cancelPromise|: + 1. If |cancelPromise| was fulfilled, then: + 1. If |writable|.[=WritableStream/[[state]]=] is "`errored`", [=reject=] + |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] with + |writable|.[=WritableStream/[[storedError]]=]. + 1. Otherwise: + 1. Perform ! [$WritableStreamDefaultControllerErrorIfNeeded$](|writable|.[=WritableStream/[[controller]]=], |reason|). + 1. Perform ! [$TransformStreamUnblockWrite$](|stream|). + 1. [=Resolve=] |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] with undefined. + 1. If |cancelPromise| was rejected with reason |r|, then: + 1. Perform ! [$WritableStreamDefaultControllerErrorIfNeeded$](|writable|.[=WritableStream/[[controller]]=], |r|). + 1. Perform ! [$TransformStreamUnblockWrite$](|stream|). + 1. [=Reject=] |controller|.[=TransformStreamDefaultController/[[finishPromise]]=] with |r|. + 1. Return |controller|.[=TransformStreamDefaultController/[[finishPromise]]=]. +
+ +
+ TransformStreamDefaultSourcePullAlgorithm(|stream|) + performs the following steps: + + 1. Assert: |stream|.[=TransformStream/[[backpressure]]=] is true. + 1. Assert: |stream|.[=TransformStream/[[backpressureChangePromise]]=] is not undefined. + 1. Perform ! [$TransformStreamSetBackpressure$](|stream|, false). + 1. Return |stream|.[=TransformStream/[[backpressureChangePromise]]=]. +
+ +

Queuing strategies

+ +

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: + + +dictionary QueuingStrategy { + unrestricted double highWaterMark; + QueuingStrategySize size; +}; + +callback QueuingStrategySize = unrestricted double (any chunk); + + +
+
highWaterMark
+
+

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

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

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

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

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

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

+ +Any object with these properties can be used when a queuing strategy object is expected. However, +we provide two built-in queuing strategy classes that provide a common vocabulary for certain +cases: {{ByteLengthQueuingStrategy}} and {{CountQueuingStrategy}}. They both make use of the +following Web IDL fragment for their constructors: + + +dictionary QueuingStrategyInit { + required unrestricted double highWaterMark; +}; + + +

The {{ByteLengthQueuingStrategy}} class

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

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

Interface definition

+ +The Web IDL definition for the {{ByteLengthQueuingStrategy}} class is given as follows: + + +[Exposed=*] +interface ByteLengthQueuingStrategy { + constructor(QueuingStrategyInit init); + + readonly attribute unrestricted double highWaterMark; + readonly attribute Function size; +}; + + +

Internal slots

+ +Instances of {{ByteLengthQueuingStrategy}} have a +\[[highWaterMark]] internal slot, storing the value given +in the constructor. + +
+ 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|, 1, "`size`", « », |globalObject|'s [=relevant + Realm=]). + 1. Set |globalObject|'s [=byte length queuing strategy size function=] to a {{Function}} that + represents a reference to |F|, with [=callback context=] equal to |globalObject|'s [=relevant + settings object=]. + +

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

+ +

Constructor and properties

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

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

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

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

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

+ +
+ The new ByteLengthQueuingStrategy(|init|) constructor steps + are: + + 1. Set [=this=].[=ByteLengthQueuingStrategy/[[highWaterMark]]=] to + |init|["{{QueuingStrategyInit/highWaterMark}}"]. +
+ +
+ The highWaterMark + getter steps are: + + 1. Return [=this=].[=ByteLengthQueuingStrategy/[[highWaterMark]]=]. +
+ +
+ The size getter steps are: + + 1. Return [=this=]'s [=relevant global object=]'s [=byte length queuing strategy size function=]. +
+ +

The {{CountQueuingStrategy}} class

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

Interface definition

+ +The Web IDL definition for the {{CountQueuingStrategy}} class is given as follows: + + +[Exposed=*] +interface CountQueuingStrategy { + constructor(QueuingStrategyInit init); + + readonly attribute unrestricted double highWaterMark; + readonly attribute Function size; +}; + + +

Internal slots

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

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

+ +

Constructor and properties

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

Creates a new {{CountQueuingStrategy}} 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 {{CountQueuingStrategy}} will cause the + corresponding stream constructor to throw. + +

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

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

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

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

+ +
+ The new CountQueuingStrategy(|init|) constructor steps are: + + 1. Set [=this=].[=CountQueuingStrategy/[[highWaterMark]]=] to + |init|["{{QueuingStrategyInit/highWaterMark}}"]. +
+ +
+ The highWaterMark + getter steps are: + + 1. Return [=this=].[=CountQueuingStrategy/[[highWaterMark]]=]. +
+ +
+ The size getter steps are: + + 1. Return [=this=]'s [=relevant global object=]'s [=count queuing strategy size function=]. +
+ +

Abstract operations

+ +The following algorithms are used by the stream constructors to extract the relevant pieces from +a {{QueuingStrategy}} dictionary. + +
+ 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| ». +
+ +

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 [=value-with-sizes=], 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.) + +In what follows, a value-with-size is a [=struct=] with the two [=struct/items=] value and size. + +

+ 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 |valueWithSize| be |container|.\[[queue]][0]. + 1. [=list/Remove=] |valueWithSize| from |container|.\[[queue]]. + 1. Set |container|.\[[queueTotalSize]] to |container|.\[[queueTotalSize]] − |valueWithSize|'s + [=value-with-size/size=]. + 1. If |container|.\[[queueTotalSize]] < 0, set |container|.\[[queueTotalSize]] to 0. (This can + occur due to rounding errors.) + 1. Return |valueWithSize|'s [=value-with-size/value=]. +
+ +
+ EnqueueValueWithSize(|container|, |value|, |size|) performs the + following steps: + + 1. Assert: |container| has \[[queue]] and \[[queueTotalSize]] internal slots. + 1. If ! [$IsNonNegativeNumber$](|size|) is false, throw a {{RangeError}} exception. + 1. If |size| is +∞, throw a {{RangeError}} exception. + 1. [=list/Append=] a new [=value-with-size=] with [=value-with-size/value=] |value| and + [=value-with-size/size=] |size| to |container|.\[[queue]]. + 1. Set |container|.\[[queueTotalSize]] to |container|.\[[queueTotalSize]] + |size|. +
+ +
+ PeekQueueValue(|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 |valueWithSize| be |container|.\[[queue]][0]. + 1. Return |valueWithSize|'s [=value-with-size/value=]. +
+ +
+ ResetQueue(|container|) + performs the following steps: + + 1. Assert: |container| has \[[queue]] and \[[queueTotalSize]] internal slots. + 1. Set |container|.\[[queue]] to a new empty [=list=]. + 1. Set |container|.\[[queueTotalSize]] to 0. +
+ +

Transferable streams

+ +Transferable streams are implemented using a special kind of identity transform which has the +[=writable side=] in one [=realm=] and the [=readable side=] in another realm. The following +abstract operations are used to implement these "cross-realm transforms". + +
+ CrossRealmTransformSendError(|port|, + |error|) performs the following steps: + + 1. Perform [$PackAndPostMessage$](|port|, "`error`", |error|), discarding the result. + +

As we are already in an errored state when this abstract operation is performed, we + cannot handle further errors, so we just discard them.

+
+ +
+ PackAndPostMessage(|port|, |type|, |value|) performs + the following steps: + + 1. Let |message| be [$OrdinaryObjectCreate$](null). + 1. Perform ! [$CreateDataProperty$](|message|, "`type`", |type|). + 1. Perform ! [$CreateDataProperty$](|message|, "`value`", |value|). + 1. Let |targetPort| be the port with which |port| is entangled, if any; otherwise let it be null. + 1. Let |options| be «[ "`transfer`" → « » ]». + 1. Run the [=message port post message steps=] providing |targetPort|, |message|, and |options|. + +

A JavaScript object is used for transfer to avoid having to duplicate the [=message + port post message steps=]. The prototype of the object is set to null to avoid interference from + {{%Object.prototype%}}.

+
+ +
+ PackAndPostMessageHandlingError(|port|, + |type|, |value|) performs the following steps: + + 1. Let |result| be [$PackAndPostMessage$](|port|, |type|, |value|). + 1. If |result| is an abrupt completion, + 1. Perform ! [$CrossRealmTransformSendError$](|port|, |result|.\[[Value]]). + 1. Return |result| as a completion record. +
+ +
+ SetUpCrossRealmTransformReadable(|stream|, + |port|) performs the following steps: + + 1. Perform ! [$InitializeReadableStream$](|stream|). + 1. Let |controller| be a [=new=] {{ReadableStreamDefaultController}}. + 1. Add a handler for |port|'s {{MessagePort/message}} event with the following steps: + 1. Let |data| be the data of the message. + 1. Assert: [$Type$](|data|) is Object. + 1. Let |type| be ! [$Get$](|data|, "`type`"). + 1. Let |value| be ! [$Get$](|data|, "`value`"). + 1. Assert: [$Type$](|type|) is String. + 1. If |type| is "`chunk`", + 1. Perform ! [$ReadableStreamDefaultControllerEnqueue$](|controller|, |value|). + 1. Otherwise, if |type| is "`close`", + 1. Perform ! [$ReadableStreamDefaultControllerClose$](|controller|). + 1. Disentangle |port|. + 1. Otherwise, if |type| is "`error`", + 1. Perform ! [$ReadableStreamDefaultControllerError$](|controller|, |value|). + 1. Disentangle |port|. + 1. Add a handler for |port|'s {{MessagePort/messageerror}} event with the following steps: + 1. Let |error| be a new "{{DataCloneError}}" {{DOMException}}. + 1. Perform ! [$CrossRealmTransformSendError$](|port|, |error|). + 1. Perform ! [$ReadableStreamDefaultControllerError$](|controller|, |error|). + 1. Disentangle |port|. + 1. Enable |port|'s [=port message queue=]. + 1. Let |startAlgorithm| be an algorithm that returns undefined. + 1. Let |pullAlgorithm| be the following steps: + 1. Perform ! [$PackAndPostMessage$](|port|, "`pull`", undefined). + 1. Return [=a promise resolved with=] undefined. + 1. Let |cancelAlgorithm| be the following steps, taking a |reason| argument: + 1. Let |result| be [$PackAndPostMessageHandlingError$](|port|, "`error`", |reason|). + 1. Disentangle |port|. + 1. If |result| is an abrupt completion, return [=a promise rejected with=] |result|.\[[Value]]. + 1. Otherwise, return [=a promise resolved with=] undefined. + 1. Let |sizeAlgorithm| be an algorithm that returns 1. + 1. Perform ! [$SetUpReadableStreamDefaultController$](|stream|, |controller|, |startAlgorithm|, + |pullAlgorithm|, |cancelAlgorithm|, 0, |sizeAlgorithm|). + +

Implementations are encouraged to explicitly handle failures from the asserts in + this algorithm, as the input might come from an untrusted context. Failure to do so could lead to + security issues.

+
+ +
+ SetUpCrossRealmTransformWritable(|stream|, + |port|) performs the following steps: + + 1. Perform ! [$InitializeWritableStream$](|stream|). + 1. Let |controller| be a [=new=] {{WritableStreamDefaultController}}. + 1. Let |backpressurePromise| be [=a new promise=]. + 1. Add a handler for |port|'s {{MessagePort/message}} event with the following steps: + 1. Let |data| be the data of the message. + 1. Assert: [$Type$](|data|) is Object. + 1. Let |type| be ! [$Get$](|data|, "`type`"). + 1. Let |value| be ! [$Get$](|data|, "`value`"). + 1. Assert: [$Type$](|type|) is String. + 1. If |type| is "`pull`", + 1. If |backpressurePromise| is not undefined, + 1. [=Resolve=] |backpressurePromise| with undefined. + 1. Set |backpressurePromise| to undefined. + 1. Otherwise, if |type| is "`error`", + 1. Perform ! [$WritableStreamDefaultControllerErrorIfNeeded$](|controller|, |value|). + 1. If |backpressurePromise| is not undefined, + 1. [=Resolve=] |backpressurePromise| with undefined. + 1. Set |backpressurePromise| to undefined. + 1. Add a handler for |port|'s {{MessagePort/messageerror}} event with the following steps: + 1. Let |error| be a new "{{DataCloneError}}" {{DOMException}}. + 1. Perform ! [$CrossRealmTransformSendError$](|port|, |error|). + 1. Perform ! [$WritableStreamDefaultControllerErrorIfNeeded$](|controller|, |error|). + 1. Disentangle |port|. + 1. Enable |port|'s [=port message queue=]. + 1. Let |startAlgorithm| be an algorithm that returns undefined. + 1. Let |writeAlgorithm| be the following steps, taking a |chunk| argument: + 1. If |backpressurePromise| is undefined, set |backpressurePromise| to + [=a promise resolved with=] undefined. + 1. Return the result of [=reacting=] to |backpressurePromise| with the following + fulfillment steps: + 1. Set |backpressurePromise| to [=a new promise=]. + 1. Let |result| be [$PackAndPostMessageHandlingError$](|port|, "`chunk`", |chunk|). + 1. If |result| is an abrupt completion, + 1. Disentangle |port|. + 1. Return [=a promise rejected with=] |result|.\[[Value]]. + 1. Otherwise, return [=a promise resolved with=] undefined. + 1. Let |closeAlgorithm| be the folowing steps: + 1. Perform ! [$PackAndPostMessage$](|port|, "`close`", undefined). + 1. Disentangle |port|. + 1. Return [=a promise resolved with=] undefined. + 1. Let |abortAlgorithm| be the following steps, taking a |reason| argument: + 1. Let |result| be [$PackAndPostMessageHandlingError$](|port|, "`error`", |reason|). + 1. Disentangle |port|. + 1. If |result| is an abrupt completion, return [=a promise rejected with=] |result|.\[[Value]]. + 1. Otherwise, return [=a promise resolved with=] undefined. + 1. Let |sizeAlgorithm| be an algorithm that returns 1. + 1. Perform ! [$SetUpWritableStreamDefaultController$](|stream|, |controller|, |startAlgorithm|, + |writeAlgorithm|, |closeAlgorithm|, |abortAlgorithm|, 1, |sizeAlgorithm|). + +

Implementations are encouraged to explicitly handle failures from the asserts in + this algorithm, as the input might come from an untrusted context. Failure to do so could lead to + security issues.

+
+ +

Miscellaneous

+ +The following abstract operations are a grab-bag of utilities. + +
+ CanTransferArrayBuffer(|O|) performs the following steps: + + 1. Assert: [$Type$](|O|) is Object. + 1. Assert: |O| has an \[[ArrayBufferData]] internal slot. + 1. If ! [$IsDetachedBuffer$](|O|) is true, return false. + 1. If [$SameValue$](|O|.\[[ArrayBufferDetachKey]], undefined) is false, return false. + 1. Return true. +
+ +
+ 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: ! [$IsDetachedBuffer$](|O|) is false. + 1. Let |arrayBufferData| be |O|.\[[ArrayBufferData]]. + 1. Let |arrayBufferByteLength| be |O|.\[[ArrayBufferByteLength]]. + 1. Perform ? [$DetachArrayBuffer$](|O|). +

This will throw an exception if |O| has an \[[ArrayBufferDetachKey]] + that is not undefined, such as a {{Memory|WebAssembly.Memory}}'s {{Memory/buffer}}. + [[WASM-JS-API-1]]

+ 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|. +
+ +
+ CloneAsUint8Array(|O|) performs the following steps: + + 1. Assert: [$Type$](|O|) is Object. + 1. Assert: |O| has an \[[ViewedArrayBuffer]] internal slot. + 1. Assert: ! [$IsDetachedBuffer$](|O|.\[[ViewedArrayBuffer]]) is false. + 1. Let |buffer| be ? [$CloneArrayBuffer$](|O|.\[[ViewedArrayBuffer]], |O|.\[[ByteOffset]], + |O|.\[[ByteLength]], {{%ArrayBuffer%}}). + 1. Let |array| be ! [$Construct$]({{%Uint8Array%}}, « |buffer| »). + 1. Return |array|. +
+ +
+ StructuredClone(|v|) performs the following steps: + + 1. Let |serialized| be ? [$StructuredSerialize$](|v|). + 1. Return ? [$StructuredDeserialize$](|serialized|, [=the current Realm=]). +
+ +

Using streams in other specifications

+ +Much of this standard concerns itself with the internal machinery of streams. Other specifications +generally do not need to worry about these details. Instead, they should interface with this +standard via the various IDL types it defines, along with the following definitions. + +Specifications should not directly inspect or manipulate the various internal slots defined in this +standard. Similarly, they should not use the abstract operations defined here. Such direct usage can +break invariants that this standard otherwise maintains. + +

If your specification wants to interface with streams in a way not supported here, +file an issue. This section is intended +to grow organically as needed. + +

Readable streams

+ +

Creation and manipulation

+ +
+ To set up a newly-[=new|created-via-Web IDL=] + {{ReadableStream}} object |stream|, given an optional algorithm pullAlgorithm, an optional algorithm cancelAlgorithm, an optional number highWaterMark (default 1), and an optional algorithm sizeAlgorithm, perform the following steps. If + given, |pullAlgorithm| and |cancelAlgorithm| may return a promise. If given, |sizeAlgorithm| must + be an algorithm accepting [=chunk=] objects and returning a number; and if given, |highWaterMark| + must be a non-negative, non-NaN number. + + 1. Let |startAlgorithm| be an algorithm that returns undefined. + 1. Let |pullAlgorithmWrapper| be an algorithm that runs these steps: + 1. Let |result| be the result of running |pullAlgorithm|, if |pullAlgorithm| was given, or null + otherwise. If this throws an exception |e|, return [=a promise rejected with=] |e|. + 1. If |result| is a {{Promise}}, then return |result|. + 1. Return [=a promise resolved with=] undefined. + 1. Let |cancelAlgorithmWrapper| be an algorithm that runs these steps given |reason|: + 1. Let |result| be the result of running |cancelAlgorithm| given |reason|, if |cancelAlgorithm| + was given, or null otherwise. If this throws an exception |e|, return + [=a promise rejected with=] |e|. + 1. If |result| is a {{Promise}}, then return |result|. + 1. Return [=a promise resolved with=] undefined. + 1. If |sizeAlgorithm| was not given, then set it to an algorithm that returns 1. + 1. Perform ! [$InitializeReadableStream$](|stream|). + 1. Let |controller| be a [=new=] {{ReadableStreamDefaultController}}. + 1. Perform ! [$SetUpReadableStreamDefaultController$](|stream|, |controller|, |startAlgorithm|, + |pullAlgorithmWrapper|, |cancelAlgorithmWrapper|, |highWaterMark|, |sizeAlgorithm|). +
+ +
+ To set up with byte reading support a + newly-[=new|created-via-Web IDL=] {{ReadableStream}} object |stream|, given an optional algorithm + pullAlgorithm, + an optional algorithm cancelAlgorithm, and an optional number highWaterMark (default 0), + perform the following steps. If given, |pullAlgorithm| and |cancelAlgorithm| may return a promise. + If given, |highWaterMark| must be a non-negative, non-NaN number. + + 1. Let |startAlgorithm| be an algorithm that returns undefined. + 1. Let |pullAlgorithmWrapper| be an algorithm that runs these steps: + 1. Let |result| be the result of running |pullAlgorithm|, if |pullAlgorithm| was given, or null + otherwise. If this throws an exception |e|, return [=a promise rejected with=] |e|. + 1. If |result| is a {{Promise}}, then return |result|. + 1. Return [=a promise resolved with=] undefined. + 1. Let |cancelAlgorithmWrapper| be an algorithm that runs these steps: + 1. Let |result| be the result of running |cancelAlgorithm|, if |cancelAlgorithm| was given, or + null otherwise. If this throws an exception |e|, return [=a promise rejected with=] |e|. + 1. If |result| is a {{Promise}}, then return |result|. + 1. Return [=a promise resolved with=] undefined. + 1. Perform ! [$InitializeReadableStream$](|stream|). + 1. Let |controller| be a [=new=] {{ReadableByteStreamController}}. + 1. Perform ! [$SetUpReadableByteStreamController$](|stream|, |controller|, |startAlgorithm|, + |pullAlgorithmWrapper|, |cancelAlgorithmWrapper|, |highWaterMark|, undefined). +
+ +
+ Creating a {{ReadableStream}} from other specifications is thus a two-step process, like so: + + 1. Let |readableStream| be a [=new=] {{ReadableStream}}. + 1. [=ReadableStream/Set up=] |readableStream| given…. +
+ +

Subclasses of {{ReadableStream}} will use the [=ReadableStream/set up=] or +[=ReadableStream/set up with byte reading support=] operations directly on the [=this=] value inside +their constructor steps. + +


+ +The following algorithms must only be used on {{ReadableStream}} instances initialized via the above +[=ReadableStream/set up=] or [=ReadableStream/set up with byte reading support=] algorithms (not, +e.g., on web-developer-created instances): + +
+ A {{ReadableStream}} |stream|'s desired size to fill up to the + high water mark is the result of running the following steps: + + 1. If |stream| is not [=ReadableStream/readable=], then return 0. + 1. If |stream|.[=ReadableStream/[[controller]]=] [=implements=] {{ReadableByteStreamController}}, + then return ! + [$ReadableByteStreamControllerGetDesiredSize$](|stream|.[=ReadableStream/[[controller]]=]). + 1. Return ! + [$ReadableStreamDefaultControllerGetDesiredSize$](|stream|.[=ReadableStream/[[controller]]=]). +
+ +

A {{ReadableStream}} needs more data if its [=ReadableStream/desired size to fill up to the high water +mark=] is greater than zero. + +

+ To close a {{ReadableStream}} |stream|: + + 1. If |stream|.[=ReadableStream/[[controller]]=] [=implements=] {{ReadableByteStreamController}}, + 1. Perform ! + [$ReadableByteStreamControllerClose$](|stream|.[=ReadableStream/[[controller]]=]). + 1. If |stream|.[=ReadableStream/[[controller]]=].[=ReadableByteStreamController/[[pendingPullIntos]]=] + is not [=list/is empty|empty=], perform ! + [$ReadableByteStreamControllerRespond$](|stream|.[=ReadableStream/[[controller]]=], 0). + 1. Otherwise, perform ! [$ReadableStreamDefaultControllerClose$](|stream|.[=ReadableStream/[[controller]]=]). +
+ +
+ To error a {{ReadableStream}} |stream| given a JavaScript + value |e|: + + 1. If |stream|.[=ReadableStream/[[controller]]=] [=implements=] {{ReadableByteStreamController}}, + then perform ! [$ReadableByteStreamControllerError$](|stream|.[=ReadableStream/[[controller]]=], + |e|). + 1. Otherwise, perform ! [$ReadableStreamDefaultControllerError$](|stream|.[=ReadableStream/[[controller]]=], + |e|). +
+ +
+ To enqueue the JavaScript value |chunk| into a + {{ReadableStream}} |stream|: + + 1. If |stream|.[=ReadableStream/[[controller]]=] [=implements=] + {{ReadableStreamDefaultController}}, + 1. Perform ! [$ReadableStreamDefaultControllerEnqueue$](|stream|.[=ReadableStream/[[controller]]=], + |chunk|). + 1. Otherwise, + 1. Assert: |stream|.[=ReadableStream/[[controller]]=] [=implements=] + {{ReadableByteStreamController}}. + 1. Assert: |chunk| is an {{ArrayBufferView}}. + 1. Let |byobView| be the [=current BYOB request view=] for |stream|. + 1. If |byobView| is non-null, and |chunk|.\[[ViewedArrayBuffer]] is + |byobView|.\[[ViewedArrayBuffer]], then: + 1. Assert: |chunk|.\[[ByteOffset]] is |byobView|.\[[ByteOffset]]. + 1. Assert: |chunk|.\[[ByteLength]] ≤ |byobView|.\[[ByteLength]]. +

These asserts ensure that the caller does not write outside the requested + range in the [=ReadableStream/current BYOB request view=]. + 1. Perform ? + [$ReadableByteStreamControllerRespond$](|stream|.[=ReadableStream/[[controller]]=], + |chunk|.\[[ByteLength]]). + 1. Otherwise, perform ? + [$ReadableByteStreamControllerEnqueue$](|stream|.[=ReadableStream/[[controller]]=], |chunk|). +

+ +
+ +The following algorithms must only be used on {{ReadableStream}} instances initialized via the above +[=ReadableStream/set up with byte reading support=] algorithm: + +
+ The current BYOB request view for a + {{ReadableStream}} |stream| is either an {{ArrayBufferView}} or null, determined by the following + steps: + + 1. Assert: |stream|.[=ReadableStream/[[controller]]=] [=implements=] + {{ReadableByteStreamController}}. + 1. Let |byobRequest| be ! + [$ReadableByteStreamControllerGetBYOBRequest$](|stream|.[=ReadableStream/[[controller]]=]). + 1. If |byobRequest| is null, then return null. + 1. Return |byobRequest|.[=ReadableStreamBYOBRequest/[[view]]=]. +
+ +Specifications must not [=ArrayBuffer/transfer=] or [=ArrayBuffer/detach=] the +[=BufferSource/underlying buffer=] of the [=ReadableStream/current BYOB request view=]. + +

Implementations could do something equivalent to transferring, e.g. if they want to +write into the memory from another thread. But they would need to make a few adjustments to how they +implement the [=ReadableStream/enqueue=] and [=ReadableStream/close=] algorithms to keep the same +observable consequences. In specification-land, transferring and detaching is just disallowed. + +Specifications should, when possible, [=ArrayBufferView/write=] into the [=ReadableStream/current +BYOB request view=] when it is non-null, and then call [=ReadableStream/enqueue=] with that view. +They should only [=ArrayBufferView/create=] a new {{ArrayBufferView}} to pass to +[=ReadableStream/enqueue=] when the [=ReadableStream/current BYOB request view=] is null, or when +they have more bytes on hand than the [=ReadableStream/current BYOB request view=]'s +[=BufferSource/byte length=]. This avoids unnecessary copies and better respects the wishes of the +stream's [=consumer=]. + +The following [=ReadableStream/pull from bytes=] algorithm implements these requirements, for the +common case where bytes are derived from a [=byte sequence=] that serves as the specification-level +representation of an [=underlying byte source=]. Note that it is conservative and leaves bytes in +the [=byte sequence=], instead of aggressively [=ReadableStream/enqueueing=] them, so callers of +this algorithm might want to use the number of remaining bytes as a [=backpressure=] signal. + +

+ To pull from bytes with a [=byte sequence=] |bytes| into a + {{ReadableStream}} |stream|: + + 1. Assert: |stream|.[=ReadableStream/[[controller]]=] [=implements=] + {{ReadableByteStreamController}}. + 1. Let |available| be |bytes|'s [=byte sequence/length=]. + 1. Let |desiredSize| be |available|. + 1. If |stream|'s [=ReadableStream/current BYOB request view=] is non-null, then set |desiredSize| + to |stream|'s [=ReadableStream/current BYOB request view=]'s [=BufferSource/byte length=]. + 1. Let |pullSize| be the smaller value of |available| and |desiredSize|. + 1. Let |pulled| be the first |pullSize| bytes of |bytes|. + 1. Remove the first |pullSize| bytes from |bytes|. + 1. If |stream|'s [=ReadableStream/current BYOB request view=] is non-null, then: + 1. [=ArrayBufferView/Write=] |pulled| into |stream|'s [=ReadableStream/current BYOB request + view=]. + 1. Perform ? [$ReadableByteStreamControllerRespond$](|stream|.[=ReadableStream/[[controller]]=], + |pullSize|). + 1. Otherwise, + 1. Set |view| to the result of [=ArrayBufferView/create|creating=] a {{Uint8Array}} from |pulled| + in |stream|'s [=relevant Realm=]. + 1. Perform ? [$ReadableByteStreamControllerEnqueue$](|stream|.[=ReadableStream/[[controller]]=], + |view|). +
+ +Specifications must not [=ArrayBuffer/write=] into the [=ReadableStream/current BYOB request view=] +or [=ReadableStream/pull from bytes=] after [=ReadableStream/closing=] the corresponding +{{ReadableStream}}. + +

Reading

+ +The following algorithms can be used on arbitrary {{ReadableStream}} instances, including ones that +are created by web developers. They can all fail in various operation-specific ways, and these +failures should be handled by the calling specification. + +
+

To get a reader for a + {{ReadableStream}} |stream|, return ? [$AcquireReadableStreamDefaultReader$](|stream|). The result + will be a {{ReadableStreamDefaultReader}}. + +

This will throw an exception if |stream| is already [=ReadableStream/locked=]. +

+ +

To read +a chunk from a {{ReadableStreamDefaultReader}} |reader|, given a [=read request=] +|readRequest|, perform ! [$ReadableStreamDefaultReaderRead$](|reader|, |readRequest|). + +

+

To read all + bytes from a {{ReadableStreamDefaultReader}} |reader|, given |successSteps|, + which is an algorithm accepting a [=byte sequence=], and |failureSteps|, which is an algorithm + accepting a JavaScript value: [=read-loop=] given |reader|, a new [=byte sequence=], + |successSteps|, and |failureSteps|. + +

+ For the purposes of the above algorithm, to read-loop given |reader|, |bytes|, + |successSteps|, and |failureSteps|: + + 1. Let |readRequest| be a new [=read request=] with the following [=struct/items=]: + : [=read request/chunk steps=], given |chunk| + :: + 1. If |chunk| is not a {{Uint8Array}} object, call |failureSteps| with a {{TypeError}} and + abort these steps. + 1. Append the bytes represented by |chunk| to |bytes|. + 1. [=Read-loop=] given |reader|, |bytes|, |successSteps|, and |failureSteps|. +

This recursion could potentially cause a stack overflow if implemented + directly. Implementations will need to mitigate this, e.g. by using a non-recursive variant + of this algorithm, or [=queue a microtask|queuing a microtask=], or using a more direct + method of byte-reading as noted below. + + : [=read request/close steps=] + :: + 1. Call |successSteps| with |bytes|. + : [=read request/error steps=], given |e| + :: + 1. Call |failureSteps| with |e|. + 1. Perform ! [$ReadableStreamDefaultReaderRead$](|reader|, |readRequest|). +

+ +

Because |reader| grants exclusive access to its corresponding {{ReadableStream}}, + the actual mechanism of how to read cannot be observed. Implementations could use a more direct + mechanism if convenient, such as acquiring and using a {{ReadableStreamBYOBReader}} instead of a + {{ReadableStreamDefaultReader}}, or accessing the chunks directly. +

+ +

To release a +{{ReadableStreamDefaultReader}} |reader|, perform ! +[$ReadableStreamDefaultReaderRelease$](|reader|). + +

To cancel a +{{ReadableStreamDefaultReader}} |reader| with |reason|, perform ! +[$ReadableStreamReaderGenericCancel$](|reader|, |reason|). The return value will be a promise +that either fulfills with undefined, or rejects with a failure reason. + +

To cancel a {{ReadableStream}} |stream| with +|reason|, return ! [$ReadableStreamCancel$](|stream|, |reason|). The return value will be a promise +that either fulfills with undefined, or rejects with a failure reason. + +

+

To tee a {{ReadableStream}} |stream|, + return ? [$ReadableStreamTee$](|stream|, true). + +

Because we pass true as the second argument to [$ReadableStreamTee$], the second + branch returned will have its [=chunks=] cloned (using HTML's [=serializable objects=] framework) + from those of the first branch. This prevents consumption of one of the branches from interfering + with the other. +

+ +

Introspection

+ +The following predicates can be used on arbitrary {{ReadableStream}} objects. However, note that +apart from checking whether or not the stream is [=ReadableStream/locked=], this direct +introspection is not possible via the public JavaScript API, and so specifications should instead +use the algorithms in [[#other-specs-rs-reading]]. (For example, instead of testing if the stream is +[=ReadableStream/readable=], attempt to [=ReadableStream/get a reader=] and handle any exception.) + +

A {{ReadableStream}} |stream| is readable if +|stream|.[=ReadableStream/[[state]]=] is "`readable`". + +

A {{ReadableStream}} |stream| is closed if +|stream|.[=ReadableStream/[[state]]=] is "`closed`". + +

A {{ReadableStream}} |stream| is errored if +|stream|.[=ReadableStream/[[state]]=] is "`errored`". + +

A {{ReadableStream}} |stream| is locked if ! [$IsReadableStreamLocked$](|stream|) returns true. + +

+

A {{ReadableStream}} |stream| is disturbed if |stream|.[=ReadableStream/[[disturbed]]=] is + true. + +

This indicates whether the stream has ever been read from or canceled. Even more so + than other predicates in this section, it is best consulted sparingly, since this is not + information web developers have access to even indirectly. As such, branching platform behavior on + it is undesirable. +

+ +

Writable streams

+ +

Creation and manipulation

+ +
+ To set up a newly-[=new|created-via-Web IDL=] + {{WritableStream}} object |stream|, given an algorithm writeAlgorithm, an optional algorithm closeAlgorithm, an optional algorithm abortAlgorithm, an optional number highWaterMark (default 1), an optional algorithm sizeAlgorithm, perform the following steps. + |writeAlgorithm| must be an algorithm that accepts a [=chunk=] object and returns a promise. If + given, |closeAlgorithm| and |abortAlgorithm| may return a promise. If given, |sizeAlgorithm| must + be an algorithm accepting [=chunk=] objects and returning a number; and if given, |highWaterMark| + must be a non-negative, non-NaN number. + + 1. Let |startAlgorithm| be an algorithm that returns undefined. + 1. Let |closeAlgorithmWrapper| be an algorithm that runs these steps: + 1. Let |result| be the result of running |closeAlgorithm|, if |closeAlgorithm| was given, or + null otherwise. If this throws an exception |e|, return [=a promise rejected with=] |e|. + 1. If |result| is a {{Promise}}, then return |result|. + 1. Return [=a promise resolved with=] undefined. + 1. Let |abortAlgorithmWrapper| be an algorithm that runs these steps given |reason|: + 1. Let |result| be the result of running |abortAlgorithm| given |reason|, if |abortAlgorithm| was + given, or null otherwise. If this throws an exception |e|, return [=a promise rejected with=] + |e|. + 1. If |result| is a {{Promise}}, then return |result|. + 1. Return [=a promise resolved with=] undefined. + 1. If |sizeAlgorithm| was not given, then set it to an algorithm that returns 1. + 1. Perform ! [$InitializeWritableStream$](|stream|). + 1. Let |controller| be a [=new=] {{WritableStreamDefaultController}}. + 1. Perform ! [$SetUpWritableStreamDefaultController$](|stream|, |controller|, |startAlgorithm|, + |writeAlgorithm|, |closeAlgorithmWrapper|, |abortAlgorithmWrapper|, |highWaterMark|, + |sizeAlgorithm|). + + Other specifications should be careful when constructing their + [=WritableStream/set up/writeAlgorithm=] to avoid [=in parallel=] reads from the given + [=chunk=], as such reads can violate the run-to-completion semantics of JavaScript. To avoid this, + they can make a synchronous copy or transfer of the given value, using operations such as + [$StructuredSerializeWithTransfer$], [=get a copy of the bytes held by the buffer source=], or + transferring an `ArrayBuffer`. An exception is when the + [=chunk=] is a {{SharedArrayBuffer}}, for which it is understood that parallel mutations are a fact + of life. + +
+ Creating a {{WritableStream}} from other specifications is thus a two-step process, like so: + + 1. Let |writableStream| be a [=new=] {{WritableStream}}. + 1. [=WritableStream/Set up=] |writableStream| given…. +
+ +

Subclasses of {{WritableStream}} will use the [=WritableStream/set up=] operation + directly on the [=this=] value inside their constructor steps.

+
+ +
+ +The following definitions must only be used on {{WritableStream}} instances initialized via the +above [=WritableStream/set up=] algorithm: + +

To error a +{{WritableStream}} |stream| given a JavaScript value |e|, perform ! +[$WritableStreamDefaultControllerErrorIfNeeded$](|stream|.[=WritableStream/[[controller]]=], |e|). + +

The signal of a {{WritableStream}} |stream| is +|stream|.[=WritableStream/[[controller]]=].[=WritableStreamDefaultController/[[abortController]]=]'s +[=AbortController/signal=]. Specifications can [=AbortSignal/add=] or [=AbortSignal/remove=] +algorithms to this {{AbortSignal}}, or consult whether it is [=AbortSignal/aborted=] and its +[=AbortSignal/abort reason=]. + +

The usual usage is, after [=WritableStream/setting up=] the {{WritableStream}}, +[=AbortSignal/add=] an algorithm to its [=WritableStream/signal=], which aborts any ongoing write +operation to the [=underlying sink=]. Then, inside the [=WritableStream/set +up/writeAlgorithm=], once the [=underlying sink=] has responded, check if the +[=WritableStream/signal=] is [=AbortSignal/aborted=], and [=reject=] the returned promise with the +signal's [=AbortSignal/abort reason=] if so. + +

Writing

+ +The following algorithms can be used on arbitrary {{WritableStream}} instances, including ones that +are created by web developers. They can all fail in various operation-specific ways, and these +failures should be handled by the calling specification. + +
+

To get a writer for a + {{WritableStream}} |stream|, return ? [$AcquireWritableStreamDefaultWriter$](|stream|). The result + will be a {{WritableStreamDefaultWriter}}. + +

This will throw an exception if |stream| is already locked. +

+ +

To write a chunk to a {{WritableStreamDefaultWriter}} |writer|, given a value |chunk|, +return ! [$WritableStreamDefaultWriterWrite$](|writer|, |chunk|). + +

To release a +{{WritableStreamDefaultWriter}} |writer|, perform ! +[$WritableStreamDefaultWriterRelease$](|writer|). + +

To close a {{WritableStream}} +|stream|, return ! [$WritableStreamClose$](|stream|). The return value will be a promise that either +fulfills with undefined, or rejects with a failure reason. + +

To abort a +{{WritableStream}} |stream| with |reason|, return ! [$WritableStreamAbort$](|stream|, |reason|). The +return value will be a promise that either fulfills with undefined, or rejects with a failure +reason. + +

Transform streams

+ +

Creation and manipulation

+ +
+ To set up a + newly-[=new|created-via-Web IDL=] {{TransformStream}} |stream| given an algorithm transformAlgorithm, an optional algorithm flushAlgorithm, and an optional algorithm cancelAlgorithm, perform the following steps. + |transformAlgorithm| and, if given, |flushAlgorithm| and |cancelAlgorithm|, may return a promise. + + 1. Let |writableHighWaterMark| be 1. + 1. Let |writableSizeAlgorithm| be an algorithm that returns 1. + 1. Let |readableHighWaterMark| be 0. + 1. Let |readableSizeAlgorithm| be an algorithm that returns 1. + 1. Let |transformAlgorithmWrapper| be an algorithm that runs these steps given a value |chunk|: + 1. Let |result| be the result of running |transformAlgorithm| given |chunk|. If this throws an + exception |e|, return [=a promise rejected with=] |e|. + 1. If |result| is a {{Promise}}, then return |result|. + 1. Return [=a promise resolved with=] undefined. + 1. Let |flushAlgorithmWrapper| be an algorithm that runs these steps: + 1. Let |result| be the result of running |flushAlgorithm|, if |flushAlgorithm| was given, or + null otherwise. If this throws an exception |e|, return [=a promise rejected with=] |e|. + 1. If |result| is a {{Promise}}, then return |result|. + 1. Return [=a promise resolved with=] undefined. + 1. Let |cancelAlgorithmWrapper| be an algorithm that runs these steps given a value |reason|: + 1. Let |result| be the result of running |cancelAlgorithm| given |reason|, if |cancelAlgorithm| + was given, or null otherwise. If this throws an exception |e|, return + [=a promise rejected with=] |e|. + 1. If |result| is a {{Promise}}, then return |result|. + 1. Return [=a promise resolved with=] undefined. + 1. Let |startPromise| be [=a promise resolved with=] undefined. + 1. Perform ! [$InitializeTransformStream$](|stream|, |startPromise|, |writableHighWaterMark|, + |writableSizeAlgorithm|, |readableHighWaterMark|, |readableSizeAlgorithm|). + 1. Let |controller| be a [=new=] {{TransformStreamDefaultController}}. + 1. Perform ! [$SetUpTransformStreamDefaultController$](|stream|, |controller|, + |transformAlgorithmWrapper|, |flushAlgorithmWrapper|, |cancelAlgorithmWrapper|). + + Other specifications should be careful when constructing their + [=TransformStream/set up/transformAlgorithm=] to avoid [=in parallel=] reads from the given + [=chunk=], as such reads can violate the run-to-completion semantics of JavaScript. To avoid this, + they can make a synchronous copy or transfer of the given value, using operations such as + [$StructuredSerializeWithTransfer$], [=get a copy of the bytes held by the buffer source=], or + transferring an `ArrayBuffer`. An exception is when the + [=chunk=] is a {{SharedArrayBuffer}}, for which it is understood that parallel mutations are a fact + of life. + +
+ Creating a {{TransformStream}} from other specifications is thus a two-step process, like so: + + 1. Let |transformStream| be a [=new=] {{TransformStream}}. + 1. [=TransformStream/Set up=] |transformStream| given…. +
+ +

Subclasses of {{TransformStream}} will use the [=TransformStream/set up=] operation + directly on the [=this=] value inside their constructor steps.

+
+ +
+ To create + an identity {{TransformStream}}: + + 1. Let |transformStream| be a [=new=] {{TransformStream}}. + 1. [=TransformStream/Set up=] |transformStream| with [=TransformStream/set up/transformAlgorithm=] set to an algorithm which, given + |chunk|, [=TransformStream/enqueues=] |chunk| in |transformStream|. + 1. Return |transformStream|. +
+ +
+ +The following algorithms must only be used on {{TransformStream}} instances initialized via the +above [=TransformStream/set up=] algorithm. Usually they are called as part of +[=TransformStream/set up/transformAlgorithm=] or +[=TransformStream/set up/flushAlgorithm=]. + +

To enqueue the JavaScript value |chunk| into a +{{TransformStream}} |stream|, perform ! +[$TransformStreamDefaultControllerEnqueue$](|stream|.[=TransformStream/[[controller]]=], |chunk|). + +

To terminate a {{TransformStream}} |stream|, +perform ! +[$TransformStreamDefaultControllerTerminate$](|stream|.[=TransformStream/[[controller]]=]). + +

To error a {{TransformStream}} |stream| given a +JavaScript value |e|, perform ! +[$TransformStreamDefaultControllerError$](|stream|.[=TransformStream/[[controller]]=], |e|). + +

Wrapping into a custom class

+ +Other specifications which mean to define custom [=transform streams=] might not want to subclass +from the {{TransformStream}} interface directly. Instead, if they need a new class, they can create +their own independent Web IDL interfaces, and use the following mixin: + + +interface mixin GenericTransformStream { + readonly attribute ReadableStream readable; + readonly attribute WritableStream writable; +}; + + +Any [=platform object=] that [=includes=] the {{GenericTransformStream}} mixin has an associated +transform, which is an actual {{TransformStream}}. + +The readable getter steps are to return [=this=]'s +[=GenericTransformStream/transform=].[=TransformStream/[[readable]]=]. + +The writable getter steps are to return [=this=]'s +[=GenericTransformStream/transform=].[=TransformStream/[[writable]]=]. + +
+ +Including the {{GenericTransformStream}} mixin will give an IDL interface the appropriate +{{GenericTransformStream/readable}} and {{GenericTransformStream/writable}} properties. To customize +the behavior of the resulting interface, its constructor (or other initialization code) must set +each instance's [=GenericTransformStream/transform=] to a [=new=] {{TransformStream}}, and then +[=TransformStream/set up|set it up=] with appropriate customizations via the +[=TransformStream/set up/transformAlgorithm=] and optionally +[=TransformStream/set up/flushAlgorithm=] arguments. + +Note: Existing examples of this pattern on the web platform include {{CompressionStream}} and +{{TextDecoderStream}}. [[COMPRESSION]] [[ENCODING]] + +

There's no need to create a wrapper class if you don't need any API beyond what the +base {{TransformStream}} class provides. The most common driver for such a wrapper is needing custom +[=constructor steps=], but if your conceptual transform stream isn't meant to be constructed, then +using {{TransformStream}} directly is fine. + +

Other stream pairs

+ +Apart from [=transform streams=], discussed above, specifications often create pairs of [=readable +stream|readable=] and [=writable stream|writable=] streams. This section gives some guidance for +such situations. + +In all such cases, specifications should use the names `readable` and `writable` for the two +properties exposing the streams in question. They should not use other names (such as +`input`/`output` or `readableStream`/`writableStream`), and they should not use methods or other +non-property means of access to the streams. + +

Duplex streams

+ +The most common readable/writable pair is a duplex stream, where the readable and +writable streams represent two sides of a single shared resource, such as a socket, connection, or +device. + +The trickiest thing to consider when specifying duplex streams is how to handle operations like +[=cancel a readable stream|canceling=] the readable side, or closing or [=abort a writable +stream|aborting=] the writable side. It might make sense to leave duplex streams "half open", with +such operations one one side not impacting the other side. Or it might be best to carry over their +effects to the other side, e.g. by specifying that your readable side's +[=ReadableStream/set up/cancelAlgorithm=] will [=WritableStream/close=] the +writable side. + +

A basic example of a duplex stream, created through +JavaScript instead of through specification prose, is found in [[#example-both]]. It illustrates +this carry-over behavior. + +Another consideration is how to handle the creation of duplex streams which need to be acquired +asynchronously, e.g. via establishing a connection. The preferred pattern here is to have a +constructible class with a promise-returning property that fulfills with the actual duplex stream +object. That duplex stream object can also then expose any information that is only available +asynchronously, e.g. connection data. The container class can then provide convenience APIs, such as +a function to close the entire connection instead of only closing individual sides. + +

An example of this more complex type of duplex +stream is the still-being-specified `WebSocketStream`. See its explainer and design +notes. + +Because duplex streams obey the `readable`/`writable` property contract, they can be used with +{{ReadableStream/pipeThrough()}}. This doesn't always make sense, but it could in cases where the +underlying resource is in fact performing some sort of transformation. + +

For an arbitrary WebSocket, piping through a +WebSocket-derived duplex stream doesn't make sense. However, if the WebSocket server is specifically +written so that it responds to incoming messages by sending the same data back in some transformed +form, then this could be useful and convenient. + +

Endpoint pairs

+ +Another type of readable/writable pair is an endpoint pair. In these cases the +readable and writable streams represent the two ends of a longer pipeline, with the intention that +web developer code insert [=transform streams=] into the middle of them. + +
+ Assuming we had a web-platform-provided function `createEndpointPair()`, web developers would write + code like so: + + + const { readable, writable } = createEndpointPair(); + readable.pipeThrough(new TransformStream(...)).pipeTo(writable); + +
+ +

WebRTC Insertable Media using +Streams is an example of this technique, with its `sender.createEncodedStreams()` and +`receiver.createEncodedStreams()` methods. + + +Despite such endpoint pairs obeying the `readable`/`writable` property contract, it never makes +sense to pass them to {{ReadableStream/pipeThrough()}}. + +

Piping

+ +
+ The result of a {{ReadableStream}} |readable| piped to a {{WritableStream}} |writable|, given an optional boolean + preventClose + (default false), an optional boolean preventAbort (default false), an optional boolean preventCancel (default + false), and an optional {{AbortSignal}} signal, is given by performing the following steps. + They will return a {{Promise}} that fulfills when the pipe completes, or rejects with an exception + if it fails. + + 1. Assert: ! [$IsReadableStreamLocked$](|readable|) is false. + 1. Assert: ! [$IsWritableStreamLocked$](|writable|) is false. + 1. Let |signalArg| be |signal| if |signal| was given, or undefined otherwise. + 1. Return ! [$ReadableStreamPipeTo$](|readable|, |writable|, |preventClose|, |preventAbort|, + |preventCancel|, |signalArg|). + +

If one doesn't care about the promise returned, referencing this concept can be a + bit awkward. The best we can suggest is "[=ReadableStream/pipe=] readable to writable".

+
+ +
+ The result of a {{ReadableStream}} |readable| piped through a {{TransformStream}} |transform|, given + an optional boolean preventClose (default false), an optional boolean preventAbort + (default false), an optional boolean preventCancel (default false), and an + optional {{AbortSignal}} signal, is given by performing the following steps. The result will be + the [=readable side=] of |transform|. + + 1. Assert: ! [$IsReadableStreamLocked$](|readable|) is false. + 1. Assert: ! [$IsWritableStreamLocked$](|transform|.[=TransformStream/[[writable]]=]) is false. + 1. Let |signalArg| be |signal| if |signal| was given, or undefined otherwise. + 1. Let |promise| be ! [$ReadableStreamPipeTo$](|readable|, + |transform|.[=TransformStream/[[writable]]=], |preventClose|, |preventAbort|, |preventCancel|, + |signalArg|). + 1. Set |promise|.\[[PromiseIsHandled]] to true. + 1. Return |transform|.[=TransformStream/[[readable]]=]. +
+ +
+ To create a proxy for a + {{ReadableStream}} |stream|, perform the following steps. The result will be a new + {{ReadableStream}} object which pulls its data from |stream|, while |stream| itself becomes + immediately [=ReadableStream/locked=] and [=ReadableStream/disturbed=]. + + 1. Let |identityTransform| be the result of creating an identity `TransformStream`. + 1. Return the result of |stream| [=ReadableStream/piped through=] |identityTransform|. +
+ +

Examples of creating streams

+ +
+ +This 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}}, {{WritableStream}}, and {{TransformStream}} +constructors. + +

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

+ +The following function creates [=readable streams=] that wrap {{WebSocket}} instances [[WEBSOCKETS]], +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"; + + 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(); + } + }); +} + + +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"); + +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. +
+ +

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. + + +function makeReadableBackpressureSocketStream(host, port) { + const socket = createBackpressureSocket(host, port); + + 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(); + } + }; + + 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(); + }, + + 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. + +

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

+ +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. + +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. + +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. + +(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.) + + +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); + if (bytesRead === 0) { + controller.close(); + } + controller.byobRequest.respond(bytesRead); + } else { + const buffer = new ArrayBuffer(DEFAULT_CHUNK_SIZE); + bytesRead = socket.readInto(buffer, 0, DEFAULT_CHUNK_SIZE); + if (bytesRead === 0) { + controller.close(); + } else { + controller.enqueue(new Uint8Array(buffer, 0, bytesRead)); + } + } + + if (bytesRead === 0) { + return; + } + + return readRepeatedly(); + }); + } + }, + + cancel() { + socket.close(); + } + }); +} + + +{{ReadableStream}} instances returned from this function can now vend [=BYOB readers=], with all of +the aforementioned benefits and caveats. + +

A readable stream with an underlying pull source

+ +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 Uint8Array(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(buffer.subarray(0, bytesRead)); + } + }, + + cancel() { + return fileHandle.close(); + } + }); +} + + +We can then create and use readable streams for files just as we could before for sockets. + +

A readable byte stream with an underlying pull source

+ +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"); + }, + + async 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, 0, v.byteLength, position); + if (bytesRead === 0) { + await fileHandle.close(); + controller.close(); + controller.byobRequest.respond(0); + } else { + position += bytesRead; + controller.byobRequest.respond(bytesRead); + } + }, + + cancel() { + return fileHandle.close(); + }, + + 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 +{{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}} [[WEBSOCKETS]]. 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: + + +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)); + + +

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

A writable stream with backpressure and success signals

+ +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 fs = require("fs").promises; + +function makeWritableFileStream(filename) { + let fileHandle; + + return new WritableStream({ + async start() { + fileHandle = await fs.open(filename, "w"); + }, + + write(chunk) { + return fileHandle.write(chunk, 0, chunk.length); + }, + + close() { + return fileHandle.close(); + }, + + abort() { + return fileHandle.close(); + } + }); +} + + +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)) + }; +} + +class WebSocketSource { + constructor(ws) { + this._ws = ws; + } + + 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!")); + }); + } + + cancel() { + this._ws.close(); + } +} + +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; + }); + + return new Promise(resolve => this._ws.onopen = resolve); + } + + write(chunk) { + this._ws.send(chunk); + } + + close() { + return this._closeWS(1000); + } + + 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); + }); + } +} + + +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(); + +writer.write("Hello"); +writer.write("web socket!"); + +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. + +

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

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. + + +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(); + +writer.write("No need to shout"); + +// 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. + +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(); + +writer.write("[1, "); + +// Logs a SyntaxError, twice: +reader.read().catch(e => console.error(e)); +writer.write("{}").catch(e => console.error(e)); + + +

Using an identity transform stream as a primitive to +create new readable streams

+ +Combining an [=identity transform stream=] with {{pipeTo()}} is a powerful way to manipulate +streams. This section contains a couple of examples of this general technique. + +It's sometimes natural to treat a promise for a [=readable stream=] as if it were a readable stream. +A simple adapter function is all that's needed: + + +function promiseToReadable(promiseForReadable) { + const ts = new TransformStream(); + + promiseForReadable + .then(readable => readable.pipeTo(ts.writable)) + .catch(reason => ts.writable.abort(reason)) + .catch(() => {}); + + return ts.readable; +} + + +Here, we pipe the data to the [=writable side=] and return the [=readable side=]. If the pipe +errors, we [=abort a writable stream|abort=] the writable side, which automatically propagates the +error to the returned readable side. If the writable side had already been errored by +{{ReadableStream/pipeTo()}}, then the {{WritableStream/abort()}} call will return a rejection, which +we can safely ignore. + +A more complex extension of this is concatenating multiple readable streams into one: + + +function concatenateReadables(readables) { + const ts = new TransformStream(); + let promise = Promise.resolve(); + + for (const readable of readables) { + promise = promise.then( + () => readable.pipeTo(ts.writable, { preventClose: true }), + reason => { + return Promise.all([ + ts.writable.abort(reason), + readable.cancel(reason) + ]); + } + ); + } + + promise.then(() => ts.writable.close(), + reason => ts.writable.abort(reason)) + .catch(() => {}); + + return ts.readable; +} + + +The error handling here is subtle because canceling the concatenated stream has to cancel all the +input streams. However, the success case is simple enough. We just pipe each stream in the +readables iterable one at a time to the [=identity transform stream=]'s [=writable +side=], and then close it when we are done. The [=readable side=] is then a concatenation of all the +chunks from all of of the streams. We return it from the function. Backpressure is applied as usual. + +

Acknowledgments

+ +The editors would like to thank +Anne van Kesteren, +AnthumChris, +Arthur Langereis, +Ben Kelly, +Bert Belder, +Brian di Palma, +Calvin Metcalf, +Dominic Tarr, +Ed Hager, +Eric Skoglund, +Forbes Lindesay, +Forrest Norvell, +Gary Blackwood, +Gorgi Kosev, +Gus Caplan, +贺师俊 (hax), +Isaac Schlueter, +isonmad, +Jake Archibald, +Jake Verbaten, +James Pryor, +Janessa Det, +Jason Orendorff, +Jeffrey Yasskin, +Jeremy Roman, +Jens Nockert, +Lennart Grahl, +Luca Casonato, +Mangala Sadhu Sangeet Singh Khalsa, +Marcos Caceres, +Marvin Hagemeister, +Mattias Buelens, +Michael Mior, +Mihai Potra, +Nidhi Jaju, +Romain Bellessort, +Simon Menke, +Stephen Sugden, +Surma, +Tab Atkins, +Tanguy Krotoff, +Thorsten Lorenz, +Till Schneidereit, +Tim Caswell, +Trevor Norris, +tzik, +Will Chan, +Youenn Fablet, +平野裕 (Yutaka Hirano), +and +Xabier Rodríguez +for their contributions to this specification. Community involvement in this specification has been +above and beyond; we couldn't have done it without you. + +This standard is written by Adam Rice (Google, ricea@chromium.org), Domenic +Denicola (Google, d@domenic.me), Mattias Buelens, and 吉野剛史 (Takeshi Yoshino, tyoshino@chromium.org).