Group: WHATWG
H1: Streams
Shortname: streams
Text Macro: TWITTER streamsstandard
Text Macro: LATESTRD 2026-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
Translation: zh-Hans https://htmlspecs.com/streams/
Translation: ko https://ko.htmlspecs.com/streams/
!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
url: sec-ecmascript-language-types-bigint-type
text: is a BigInt
text: is not a BigInt
url: sec-ecmascript-language-types-boolean-type
text: is a Boolean
text: is not a Boolean
url: sec-ecmascript-language-types-number-type
text: is a Number
text: is not a Number
url: sec-ecmascript-language-types-string-type
text: is a String
text: is not a String
url: sec-ecmascript-language-types-symbol-type
text: is a Symbol
text: is not a Symbol
url: sec-object-type
text: is an Object
text: is not an Object
type: abstract-op
text: IsInteger; url: #sec-isinteger
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(async_sequence 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]].\[[ByteLength]] 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]].\[[ByteLength]] 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 Uint8Array? 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 |iterator| be the result of [=opening an async sequence=] |asyncIterable|.
1. Let |startAlgorithm| be an algorithm that returns undefined.
1. Let |pullAlgorithm| be the following steps:
1. Let |nextPromise| be the result of [=get an async iterator next value|getting the next value=]
of |iterator|.
1. Return the result of [=reacting=] to |nextPromise|:
- If |nextPromise| was fulfilled with value |v|:
1. If |v| is [=end of iteration=], perform ! [$ReadableStreamDefaultControllerClose$](|stream|.[=ReadableStream/[[controller]]=]).
1. Otherwise, perform ! [$ReadableStreamDefaultControllerEnqueue$](|stream|.[=ReadableStream/[[controller]]=], |v|).
- If |nextPromise| was rejected with reason |r|, perform ! [$ReadableStreamDefaultControllerError$](|stream|.[=ReadableStream/[[controller]]=], |r|).
1. Let |cancelAlgorithm| be the following steps, given |reason|:
1. Return the result of [=close an async iterator|closing=] |iterator| with |reason|.
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. Let |filledPullIntos| be the result of performing
! [$ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue$](|controller|).
1. [=list/For each=] |filledPullInto| of |filledPullIntos|,
1. Perform !
[$ReadableByteStreamControllerCommitPullIntoDescriptor$](|stream|, |filledPullInto|).
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: ! [$IsDetachedBuffer$](|pullIntoDescriptor|'s [=pull-into descriptor/buffer=]) is 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. Let |descriptorBuffer| be |pullIntoDescriptor|'s [=pull-into descriptor/buffer=].
1. Let |queueBuffer| be |headOfQueue|'s [=readable byte stream queue entry/buffer=].
1. Let |queueByteOffset| be |headOfQueue|'s [=readable byte stream queue entry/byte offset=].
1. Assert: ! [$CanCopyDataBlockBytes$](|descriptorBuffer|, |destStart|, |queueBuffer|,
|queueByteOffset|, |bytesToCopy|) is true.
If this assertion were to fail (due to a bug in this specification or
its implementation), then the next step may read from or write to potentially invalid memory.
The user agent should always check this assertion, and stop in an [=implementation-defined=]
manner if it fails (e.g. by crashing the process, or by
erroring the stream).
1. Perform ! [$CopyDataBlockBytes$](|descriptorBuffer|.\[[ArrayBufferData]], |destStart|,
|queueBuffer|.\[[ArrayBufferData]], |queueByteOffset|, |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. Let |filledPullIntos| be a new empty [=list=].
1. [=While=] |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=] is not
[=list/is empty|empty=],
1. If |controller|.[=ReadableByteStreamController/[[queueTotalSize]]=] is 0, then [=iteration/break=].
1. Let |pullIntoDescriptor| be
|controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=][0].
1. If ! [$ReadableByteStreamControllerFillPullIntoDescriptorFromQueue$](|controller|,
|pullIntoDescriptor|) is true,
1. Perform ! [$ReadableByteStreamControllerShiftPendingPullInto$](|controller|).
1. [=list/Append=] |pullIntoDescriptor| to |filledPullIntos|.
1. Return |filledPullIntos|.
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. Let |filledPullIntos| be a new empty [=list=].
1. [=While=] |filledPullIntos|'s [=list/size=] < !
[$ReadableStreamGetNumReadIntoRequests$](|stream|),
1. Let |pullIntoDescriptor| be !
[$ReadableByteStreamControllerShiftPendingPullInto$](|controller|).
1. [=list/Append=] |pullIntoDescriptor| to |filledPullIntos|.
1. [=list/For each=] |filledPullInto| of |filledPullIntos|,
1. Perform ! [$ReadableByteStreamControllerCommitPullIntoDescriptor$](|stream|,
|filledPullInto|).
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. Let |filledPullIntos| be the result of performing
! [$ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue$](|controller|).
1. [=list/For each=] |filledPullInto| of |filledPullIntos|,
1. Perform !
[$ReadableByteStreamControllerCommitPullIntoDescriptor$](|controller|.[=ReadableByteStreamController/[[stream]]=],
|filledPullInto|).
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. Let |filledPullIntos| be the result of performing
! [$ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue$](|controller|).
1. Perform !
[$ReadableByteStreamControllerCommitPullIntoDescriptor$](|controller|.[=ReadableByteStreamController/[[stream]]=],
|pullIntoDescriptor|).
1. [=list/For each=] |filledPullInto| of |filledPullIntos|,
1. Perform !
[$ReadableByteStreamControllerCommitPullIntoDescriptor$](|controller|.[=ReadableByteStreamController/[[stream]]=],
|filledPullInto|).
ReadableByteStreamControllerRespondInternal(|controller|,
|bytesWritten|) performs the following steps:
1. Let |firstDescriptor| be |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=][0].
1. Assert: ! [$CanTransferArrayBuffer$](|firstDescriptor|'s [=pull-into descriptor/buffer=]) is true.
1. Perform ! [$ReadableByteStreamControllerInvalidateBYOBRequest$](|controller|).
1. Let |state| be
|controller|.[=ReadableByteStreamController/[[stream]]=].[=ReadableStream/[[state]]=].
1. If |state| is "`closed`",
1. Assert: |bytesWritten| is 0.
1. Perform ! [$ReadableByteStreamControllerRespondInClosedState$](|controller|,
|firstDescriptor|).
1. Otherwise,
1. Assert: |state| is "`readable`".
1. Assert: |bytesWritten| > 0.
1. Perform ? [$ReadableByteStreamControllerRespondInReadableState$](|controller|, |bytesWritten|,
|firstDescriptor|).
1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$](|controller|).
ReadableByteStreamControllerRespondWithNewView(|controller|,
|view|) performs the following steps:
1. Assert: |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=] is not [=list/is
empty|empty=].
1. Assert: ! [$IsDetachedBuffer$](|view|.\[[ViewedArrayBuffer]]) is false.
1. Let |firstDescriptor| be |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=][0].
1. Let |state| be
|controller|.[=ReadableByteStreamController/[[stream]]=].[=ReadableStream/[[state]]=].
1. If |state| is "`closed`",
1. If |view|.\[[ByteLength]] is not 0, throw a {{TypeError}} exception.
1. Otherwise,
1. Assert: |state| is "`readable`".
1. If |view|.\[[ByteLength]] is 0, throw a {{TypeError}} exception.
1. If |firstDescriptor|'s [=pull-into descriptor/byte offset=] + |firstDescriptor|' [=pull-into
descriptor/bytes filled=] is not |view|.\[[ByteOffset]], throw a {{RangeError}} exception.
1. If |firstDescriptor|'s [=pull-into descriptor/buffer byte length=] is not
|view|.\[[ViewedArrayBuffer]].\[[ByteLength]], throw a {{RangeError}} exception.
1. If |firstDescriptor|'s [=pull-into descriptor/bytes filled=] + |view|.\[[ByteLength]] >
|firstDescriptor|'s [=pull-into descriptor/byte length=], throw a {{RangeError}} exception.
1. Let |viewByteLength| be |view|.\[[ByteLength]].
1. Set |firstDescriptor|'s [=pull-into descriptor/buffer=] to ?
[$TransferArrayBuffer$](|view|.\[[ViewedArrayBuffer]]).
1. Perform ? [$ReadableByteStreamControllerRespondInternal$](|controller|, |viewByteLength|).
ReadableByteStreamControllerShiftPendingPullInto(|controller|)
performs the following steps:
1. Assert: |controller|.[=ReadableByteStreamController/[[byobRequest]]=] is null.
1. Let |descriptor| be |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=][0].
1. [=list/Remove=] |descriptor| from
|controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=].
1. Return |descriptor|.
ReadableByteStreamControllerShouldCallPull(|controller|)
performs the following steps:
1. Let |stream| be |controller|.[=ReadableByteStreamController/[[stream]]=].
1. If |stream|.[=ReadableStream/[[state]]=] is not "`readable`", return false.
1. If |controller|.[=ReadableByteStreamController/[[closeRequested]]=] is true, return false.
1. If |controller|.[=ReadableByteStreamController/[[started]]=] is false, return false.
1. If ! [$ReadableStreamHasDefaultReader$](|stream|) is true and !
[$ReadableStreamGetNumReadRequests$](|stream|) > 0, return true.
1. If ! [$ReadableStreamHasBYOBReader$](|stream|) is true and !
[$ReadableStreamGetNumReadIntoRequests$](|stream|) > 0, return true.
1. Let |desiredSize| be ! [$ReadableByteStreamControllerGetDesiredSize$](|controller|).
1. Assert: |desiredSize| is not null.
1. If |desiredSize| > 0, return true.
1. Return false.
SetUpReadableByteStreamController(|stream|,
|controller|, |startAlgorithm|, |pullAlgorithm|, |cancelAlgorithm|, |highWaterMark|,
|autoAllocateChunkSize|) performs the following steps:
1. Assert: |stream|.[=ReadableStream/[[controller]]=] is undefined.
1. If |autoAllocateChunkSize| is not undefined,
1. Assert: ! [$IsInteger$](|autoAllocateChunkSize|) is true.
1. Assert: |autoAllocateChunkSize| is positive.
1. Set |controller|.[=ReadableByteStreamController/[[stream]]=] to |stream|.
1. Set |controller|.[=ReadableByteStreamController/[[pullAgain]]=] and
|controller|.[=ReadableByteStreamController/[[pulling]]=] to false.
1. Set |controller|.[=ReadableByteStreamController/[[byobRequest]]=] to null.
1. Perform ! [$ResetQueue$](|controller|).
1. Set |controller|.[=ReadableByteStreamController/[[closeRequested]]=] and
|controller|.[=ReadableByteStreamController/[[started]]=] to false.
1. Set |controller|.[=ReadableByteStreamController/[[strategyHWM]]=] to |highWaterMark|.
1. Set |controller|.[=ReadableByteStreamController/[[pullAlgorithm]]=] to |pullAlgorithm|.
1. Set |controller|.[=ReadableByteStreamController/[[cancelAlgorithm]]=] to |cancelAlgorithm|.
1. Set |controller|.[=ReadableByteStreamController/[[autoAllocateChunkSize]]=] to
|autoAllocateChunkSize|.
1. Set |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=] to a new empty [=list=].
1. Set |stream|.[=ReadableStream/[[controller]]=] to |controller|.
1. Let |startResult| be the result of performing |startAlgorithm|.
1. Let |startPromise| be [=a promise resolved with=] |startResult|.
1. [=Upon fulfillment=] of |startPromise|,
1. Set |controller|.[=ReadableByteStreamController/[[started]]=] to true.
1. Assert: |controller|.[=ReadableByteStreamController/[[pulling]]=] is false.
1. Assert: |controller|.[=ReadableByteStreamController/[[pullAgain]]=] is false.
1. Perform ! [$ReadableByteStreamControllerCallPullIfNeeded$](|controller|).
1. [=Upon rejection=] of |startPromise| with reason |r|,
1. Perform ! [$ReadableByteStreamControllerError$](|controller|, |r|).
SetUpReadableByteStreamControllerFromUnderlyingSource(|stream|,
|underlyingSource|, |underlyingSourceDict|, |highWaterMark|) performs the following steps:
1. Let |controller| be a [=new=] {{ReadableByteStreamController}}.
1. Let |startAlgorithm| be an algorithm that returns undefined.
1. Let |pullAlgorithm| be an algorithm that returns [=a promise resolved with=] undefined.
1. Let |cancelAlgorithm| be an algorithm that returns [=a promise resolved with=] undefined.
1. If |underlyingSourceDict|["{{UnderlyingSource/start}}"] [=map/exists=], then set
|startAlgorithm| to an algorithm which returns the result of [=invoking=]
|underlyingSourceDict|["{{UnderlyingSource/start}}"] with argument list
« |controller| » and [=callback this value=] |underlyingSource|.
1. If |underlyingSourceDict|["{{UnderlyingSource/pull}}"] [=map/exists=], then set
|pullAlgorithm| to an algorithm which returns the result of [=invoking=]
|underlyingSourceDict|["{{UnderlyingSource/pull}}"] with argument list
« |controller| » and [=callback this value=] |underlyingSource|.
1. If |underlyingSourceDict|["{{UnderlyingSource/cancel}}"] [=map/exists=], then set
|cancelAlgorithm| to an algorithm which takes an argument |reason| and returns the result of
[=invoking=] |underlyingSourceDict|["{{UnderlyingSource/cancel}}"] with argument list
« |reason| » and [=callback this value=] |underlyingSource|.
1. Let |autoAllocateChunkSize| be
|underlyingSourceDict|["{{UnderlyingSource/autoAllocateChunkSize}}"], if it [=map/exists=], or
undefined otherwise.
1. If |autoAllocateChunkSize| is 0, then throw a {{TypeError}} exception.
1. Perform ? [$SetUpReadableByteStreamController$](|stream|, |controller|, |startAlgorithm|,
|pullAlgorithm|, |cancelAlgorithm|, |highWaterMark|, |autoAllocateChunkSize|).
Writable streams
Using writable streams
The usual way to write to a writable stream is to simply [=piping|pipe=] a [=readable stream=] to
it. This ensures that [=backpressure=] is respected, so that if the writable stream's [=underlying
sink=] is not able to accept data as fast as the readable stream can produce it, the readable
stream is informed of this and has a chance to slow down its data production.
readableStream.pipeTo(writableStream)
.then(() => console.log("All data successfully written!"))
.catch(e => console.error("Something went wrong!", e));
You can also write directly to writable streams by acquiring a [=writer=] and using its
{{WritableStreamDefaultWriter/write()}} and {{WritableStreamDefaultWriter/close()}} methods. Since
writable streams queue any incoming writes, and take care internally to forward them to the
[=underlying sink=] in sequence, you can indiscriminately write to a writable stream without much
ceremony:
function writeArrayToStream(array, writableStream) {
const writer = writableStream.getWriter();
array.forEach(chunk => writer.write(chunk).catch(() => {}));
return writer.close();
}
writeArrayToStream([1, 2, 3, 4, 5], writableStream)
.then(() => console.log("All done!"))
.catch(e => console.error("Error with the stream: " + e));
Note how we use .catch(() => {}) to suppress any rejections from the
{{WritableStreamDefaultWriter/write()}} method; we'll be notified of any fatal errors via a
rejection of the {{WritableStreamDefaultWriter/close()}} method, and leaving them un-caught would
cause potential {{unhandledrejection}} events and console warnings.
In the previous example we only paid attention to the success or failure of the entire stream, by
looking at the promise returned by the writer's {{WritableStreamDefaultWriter/close()}} method.
That promise will reject if anything goes wrong with the stream—initializing it, writing to it, or
closing it. And it will fulfill once the stream is successfully closed. Often this is all you care
about.
However, if you care about the success of writing a specific [=chunk=], you can use the promise
returned by the writer's {{WritableStreamDefaultWriter/write()}} method:
writer.write("i am a chunk of data")
.then(() => console.log("chunk successfully written!"))
.catch(e => console.error(e));
What "success" means is up to a given stream instance (or more precisely, its [=underlying sink=])
to decide. For example, for a file stream it could simply mean that the OS has accepted the write,
and not necessarily that the chunk has been flushed to disk. Some streams might not be able to
give such a signal at all, in which case the returned promise will fulfill immediately.
The {{WritableStreamDefaultWriter/desiredSize}} and {{WritableStreamDefaultWriter/ready}}
properties of
writable stream writers allow [=producers=] to more precisely respond to flow
control signals from the stream, to keep memory usage below the stream's specified [=high water
mark=]. The following example writes an infinite sequence of random bytes to a stream, using
{{WritableStreamDefaultWriter/desiredSize}} to determine how many bytes to generate at a given
time, and using {{WritableStreamDefaultWriter/ready}} to wait for the [=backpressure=] to subside.
async function writeRandomBytesForever(writableStream) {
const writer = writableStream.getWriter();
while (true) {
await writer.ready;
const bytes = new Uint8Array(writer.desiredSize);
crypto.getRandomValues(bytes);
// Purposefully don't await; awaiting writer.ready is enough.
writer.write(bytes).catch(() => {});
}
}
writeRandomBytesForever(myWritableStream).catch(e => console.error("Something broke", e));
Note how we don't
await the promise returned by
{{WritableStreamDefaultWriter/write()}}; this would be redundant with
awaiting the
{{WritableStreamDefaultWriter/ready}} promise. Additionally, similar to
a previous example, we use the
.catch(() =>
{}) pattern on the promises returned by {{WritableStreamDefaultWriter/write()}}; in this
case we'll be notified about any failures
awaiting the {{WritableStreamDefaultWriter/ready}} promise.
To further emphasize how it's a bad idea to await the promise returned by
{{WritableStreamDefaultWriter/write()}}, consider a modification of the above example, where we
continue to use the {{WritableStreamDefaultWriter}} interface directly, but we don't control how
many bytes we have to write at a given time. In that case, the [=backpressure=]-respecting code
looks the same:
async function writeSuppliedBytesForever(writableStream, getBytes) {
const writer = writableStream.getWriter();
while (true) {
await writer.ready;
const bytes = getBytes();
writer.write(bytes).catch(() => {});
}
}
Unlike the previous example, where—because we were always writing exactly
{{WritableStreamDefaultWriter/desiredSize|writer.desiredSize}} bytes each time—the
{{WritableStreamDefaultWriter/write()}} and {{WritableStreamDefaultWriter/ready}} promises were
synchronized, in this case it's quite possible that the {{WritableStreamDefaultWriter/ready}}
promise fulfills before the one returned by {{WritableStreamDefaultWriter/write()}} does.
Remember, the {{WritableStreamDefaultWriter/ready}} promise fulfills when the [=desired size to
fill a stream's internal queue|desired size=] becomes positive, which might be before the write
succeeds (especially in cases with a larger [=high water mark=]).
In other words, awaiting the return value of {{WritableStreamDefaultWriter/write()}}
means you never queue up writes in the stream's [=internal queue=], instead only executing a write
after the previous one succeeds, which can result in low throughput.
The {{WritableStream}} class
The {{WritableStream}} represents a [=writable stream=].
Interface definition
The Web IDL definition for the {{WritableStream}} class is given as follows:
[Exposed=*, Transferable]
interface WritableStream {
constructor(optional object underlyingSink, optional QueuingStrategy strategy = {});
readonly attribute boolean locked;
Promise 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. If |controller|.[=WritableStreamDefaultController/[[strategySizeAlgorithm]]=] is undefined, then:
1. Assert: |controller|.[=WritableStreamDefaultController/[[stream]]=].[=WritableStream/[[state]]=] is not
"`writable`".
1. Return 1.
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("...", { method: "POST", body: readable, duplex: "half" }).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: |data| [=is an Object=].
1. Let |type| be ! [$Get$](|data|, "`type`").
1. Let |value| be ! [$Get$](|data|, "`value`").
1. Assert: |type| [=is a 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: |data| [=is an Object=].
1. Let |type| be ! [$Get$](|data|, "`type`").
1. Let |value| be ! [$Get$](|data|, "`value`").
1. Assert: |type| [=is a 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 following 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: |O| [=is an 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 |v| [=is not a 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: |O| [=is an 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=]).
CanCopyDataBlockBytes(|toBuffer|, |toIndex|,
|fromBuffer|, |fromIndex|, |count|) performs the following steps:
1. Assert: |toBuffer| [=is an Object=].
1. Assert: |toBuffer| has an \[[ArrayBufferData]] internal slot.
1. Assert: |fromBuffer| [=is an Object=].
1. Assert: |fromBuffer| has an \[[ArrayBufferData]] internal slot.
1. If |toBuffer| is |fromBuffer|, return false.
1. If ! [$IsDetachedBuffer$](|toBuffer|) is true, return false.
1. If ! [$IsDetachedBuffer$](|fromBuffer|) is true, return false.
1. If |toIndex| + |count| > |toBuffer|.\[[ArrayBufferByteLength]], return false.
1. If |fromIndex| + |count| > |fromBuffer|.\[[ArrayBufferByteLength]], return false.
1. Return true.
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.
To create
a {{ReadableStream}} from an [=async sequence=] |sequence|:
1. Return ? [$ReadableStreamFromIterable$](|sequence|).
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 set up a newly-[=new|created-via-Web IDL=]
{{ReadableStreamDefaultReader}} |reader| for a {{ReadableStream}} |stream|,
perform ? [$SetUpReadableStreamDefaultReader$](|reader|, |stream|).
Subclasses of {{ReadableStreamDefaultReader}} will use the
[=ReadableStreamDefaultReader/set up=] operation directly on the [=this=] value inside their
constructor steps.
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 set up a newly-[=new|created-via-Web IDL=]
{{WritableStreamDefaultWriter}} |writer| for a {{WritableStream}} |stream|,
perform ? [$SetUpWritableStreamDefaultWriter$](|writer|, |stream|).
Subclasses of {{WritableStreamDefaultWriter}} will use the
[=WritableStreamDefaultWriter/set up=] operation directly on the [=this=] value inside their
constructor steps.
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 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,
Shivendra Kumar,
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).