diff --git a/index.bs b/index.bs
index 9ef01d85..1649c4c4 100644
--- a/index.bs
+++ b/index.bs
@@ -3,7 +3,7 @@ Group: WHATWG
H1: Streams
Shortname: streams
Text Macro: TWITTER streamsstandard
-Text Macro: LATESTRD 2024-02
+Text Macro: LATESTRD 2024-08
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-08.bs b/review-drafts/2024-08.bs
new file mode 100644
index 00000000..2aadea02
--- /dev/null
+++ b/review-drafts/2024-08.bs
@@ -0,0 +1,8321 @@
+
+Group: WHATWG
+Status: RD
+Date: 2024-08-19
+H1: Streams
+Shortname: streams
+Text Macro: TWITTER streamsstandard
+Text Macro: LATESTRD 2024-08
+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
+
+
+
+spec:webidl; type:dfn; text:resolve
+spec:webidl; type:dfn; text:new
+spec:infra; type:dfn; text:list
+spec:html; type:dfn; text:entangle
+spec:html; type:dfn; text:message port post message steps
+spec:html; type:dfn; text:port message queue
+
+
+
+urlPrefix: https://tc39.es/ecma262/; spec: ECMASCRIPT
+ type: interface
+ text: ArrayBuffer; url: #sec-arraybuffer-objects
+ text: DataView; url: #sec-dataview-objects
+ text: SharedArrayBuffer; url: #sec-sharedarraybuffer-objects
+ text: Uint8Array; url: #sec-typedarray-objects
+ 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: 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
+ type: abstract-op
+ text: IsInteger; url: #sec-isinteger
+ text: Type; url: #sec-ecmascript-data-types-and-values
+ text: TypeError; url: #sec-native-error-types-used-in-this-standard-typeerror; type: exception
+ text: map; url: #sec-array.prototype.map; type: method; for: Array.prototype
+
+
+
+
+Introduction
+
+
+
+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 cancel(optional any reason);
+ ReadableStreamReader getReader(optional ReadableStreamGetReaderOptions options = {});
+ ReadableStream pipeThrough(ReadableWritablePair transform, optional StreamPipeOptions options = {});
+ Promise pipeTo(WritableStream destination, optional StreamPipeOptions options = {});
+ sequence tee();
+
+ async iterable(optional ReadableStreamIteratorOptions options = {});
+};
+
+typedef (ReadableStreamDefaultReader or ReadableStreamBYOBReader) ReadableStreamReader;
+
+enum ReadableStreamReaderMode { "byob" };
+
+dictionary ReadableStreamGetReaderOptions {
+ ReadableStreamReaderMode mode;
+};
+
+dictionary ReadableStreamIteratorOptions {
+ boolean preventCancel = false;
+};
+
+dictionary ReadableWritablePair {
+ required ReadableStream readable;
+ required WritableStream writable;
+};
+
+dictionary StreamPipeOptions {
+ boolean preventClose = false;
+ boolean preventAbort = false;
+ boolean preventCancel = false;
+ AbortSignal signal;
+};
+
+
+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 (ReadableStreamController controller);
+callback UnderlyingSourceCancelCallback = Promise (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 closed;
+
+ Promise 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 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 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 Slot |
+ Description (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 Slot |
+ Description (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 Slot |
+ Description (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
await
ing 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
+
await
ing 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, await
ing 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 abort(optional any reason);
+ Promise 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 (any chunk, WritableStreamDefaultController controller);
+callback UnderlyingSinkCloseCallback = Promise ();
+callback UnderlyingSinkAbortCallback = Promise (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 closed;
+ readonly attribute unrestricted double? desiredSize;
+ readonly attribute Promise ready;
+
+ Promise abort(optional any reason);
+ Promise close();
+ undefined releaseLock();
+ Promise 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 Slot |
+ Description (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| »,
+ exception behavior "rethrow
", 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 Slot |
+ Description (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 {{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 (TransformStreamDefaultController controller);
+callback TransformerTransformCallback = Promise (any chunk, TransformStreamDefaultController controller);
+callback TransformerCancelCallback = Promise (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 Slot |
+ Description (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();
+ await readable.pipeThrough(new TransformStream(...)).pipeTo(writable);
+
+
+
+WebRTC Encoded Transform
+is an example of this technique, with its {{RTCRtpScriptTransformer}} interface which has
+both `readable` and `writable` attributes.
+
+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));
+
+
+
+
+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).