Streams

Living Standard — Last Updated

Participate:
GitHub whatwg/streams (new issue, open issues)
Chat on Matrix
Commits:
GitHub whatwg/streams/commits
Snapshot as of this commit
@streamsstandard
Tests:
web-platform-tests streams/ (ongoing work)
Translations (non-normative):
日本語
简体中文
한국어
Demos:
streams.spec.whatwg.org/demos

Abstract

This specification provides APIs for creating, composing, and consuming streams of data that map efficiently to low-level I/O primitives.

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

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.

2. 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 Uint8Arrays, instead of single bytes.

2.1. 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 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 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 tee() method. This will lock the stream, making it no longer directly usable; however, it will create two new streams, called branches, which can be consumed independently.

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 getReader() method.

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

2.3. 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 transform() method is present on the transformer object.

Some examples of potential transform streams include:

2.4. 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 pipeTo() method, or it can be piped through one or more transform streams first, using its 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 controller.desiredSize (or byteController.desiredSize) value, and can then adjust its rate of data flow accordingly. This value is derived from the writer.desiredSize corresponding to the ultimate sink, which gets updated as the ultimate sink finishes writing chunks. The pipeTo() method used to construct the chain automatically ensures this information propagates back through the pipe chain.

When teeing a readable stream, the backpressure signals from its two branches will aggregate, such that if neither branch is read from, a backpressure signal will be sent to the underlying source of the original stream.

Piping locks the readable and writable streams, preventing them from being manipulated for the duration of the pipe operation. This allows the implementation to perform important optimizations, such as directly shuttling data from the underlying source to the underlying sink while bypassing many of the intermediate queues.

2.5. Internal queues and queuing strategies

Both readable and writable streams maintain internal queues, which they use for similar purposes. In the case of a readable stream, the internal queue contains chunks that have been enqueued by the underlying source, but not yet read by the consumer. In the case of a writable stream, the internal queue contains chunks which have been written to the stream by the producer, but not yet processed and acknowledged by the underlying sink.

A queuing strategy is an object that determines how a stream should signal backpressure based on the state of its internal queue. The queuing strategy assigns a size to each chunk, and compares the total size of all chunks in the queue to a specified number, known as the high water mark. The resulting difference, high water mark minus total size, is used to determine the desired size to fill the stream’s queue.

For readable streams, an underlying source can use this desired size as a backpressure signal, slowing down chunk generation so as to try to keep the desired size above or at zero. For writable streams, a producer can behave similarly, avoiding writes that would cause the desired size to go negative.

Concretely, a queuing strategy for web developer–created streams is given by any JavaScript object with a highWaterMark property. For byte streams the highWaterMark always has units of bytes. For other streams the default unit is chunks, but a size() function can be included in the strategy object which returns the size for a given chunk. This permits the highWaterMark to be specified in arbitrary floating-point units.

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

2.6. Locking

A readable stream reader, or simply reader, is an object that allows direct reading of chunks from a readable stream. Without a reader, a consumer can only perform high-level operations on the readable stream: canceling the stream, or piping the readable stream to a writable stream. A reader is acquired via the stream’s 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 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 or 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 defaultReader.releaseLock(), byobReader.releaseLock(), or writer.releaseLock() method, as appropriate.

3. 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 § 8.1 Queue-with-sizes. [IEEE-754]

4. Readable streams

4.1. Using readable streams

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

By returning promises from your write() implementation, you can signal backpressure to the readable stream.

Although readable streams will usually be used by piping them to a writable stream, you can also read them directly by acquiring a reader and using its read() method to get successive chunks. For example, this code logs the next chunk in the stream, if available:
const reader = readableStream.getReader();

reader.read().then(
  ({ value, done }) => {
    if (done) {
      console.log("The stream was already closed!");
    } else {
      console.log(value);
    }
  },
  e => console.error("The stream became errored and cannot be read from!", e)
);

This more manual method of reading a stream is mainly useful for library authors building new high-level operations on streams, beyond the provided ones of piping and teeing.

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 min option of 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);

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

4.2.1. Interface definition

The Web IDL definition for the ReadableStream class is given as follows:

[Exposed=*, Transferable]
interface ReadableStream {
  constructor(optional object underlyingSource, optional QueuingStrategy strategy = {});

  static ReadableStream from(any asyncIterable);

  readonly attribute boolean locked;

  Promise<undefined> cancel(optional any reason);
  ReadableStreamReader getReader(optional ReadableStreamGetReaderOptions options = {});
  ReadableStream pipeThrough(ReadableWritablePair transform, optional StreamPipeOptions options = {});
  Promise<undefined> pipeTo(WritableStream destination, optional StreamPipeOptions options = {});
  sequence<ReadableStream> tee();

  async_iterable<any>(optional ReadableStreamIteratorOptions options = {});
};

typedef (ReadableStreamDefaultReader or ReadableStreamBYOBReader) ReadableStreamReader;

enum ReadableStreamReaderMode { "byob" };

dictionary ReadableStreamGetReaderOptions {
  ReadableStreamReaderMode mode;
};

dictionary ReadableStreamIteratorOptions {
  boolean preventCancel = false;
};

dictionary ReadableWritablePair {
  required ReadableStream readable;
  required WritableStream writable;
};

dictionary StreamPipeOptions {
  boolean preventClose = false;
  boolean preventAbort = false;
  boolean preventCancel = false;
  AbortSignal signal;
};

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

4.2.3. The underlying source API

The ReadableStream() constructor accepts as its first argument a JavaScript object representing the underlying source. Such objects can contain any of the following properties:

dictionary UnderlyingSource {
  UnderlyingSourceStartCallback start;
  UnderlyingSourcePullCallback pull;
  UnderlyingSourceCancelCallback cancel;
  ReadableStreamType type;
  [EnforceRange] unsigned long long autoAllocateChunkSize;
};

typedef (ReadableStreamDefaultController or ReadableByteStreamController) ReadableStreamController;

callback UnderlyingSourceStartCallback = any (ReadableStreamController controller);
callback UnderlyingSourcePullCallback = Promise<undefined> (ReadableStreamController controller);
callback UnderlyingSourceCancelCallback = Promise<undefined> (optional any reason);

enum ReadableStreamType { "bytes" };
start(controller), of type UnderlyingSourceStartCallback

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 § 10.1 A readable stream with an underlying push source (no backpressure support), or to acquire access to a pull source, as in § 10.4 A readable stream with an underlying pull source.

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), of type UnderlyingSourcePullCallback

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

For push sources, this can be used to resume a paused flow, as in § 10.2 A readable stream with an underlying push source and backpressure support. For pull sources, it is used to acquire new chunks to enqueue into the stream, as in § 10.4 A readable stream with an underlying pull source.

This function will not be called until 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 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), of type UnderlyingSourceCancelCallback

A function that is called whenever the consumer cancels the stream, via stream.cancel() or 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 pipeTo() method for more details.

For all streams, this is generally used to release access to the underlying resource; see for example § 10.1 A readable stream with an underlying push source (no backpressure support).

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 close and 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), of type ReadableStreamType

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 getReader() method. It also affects the controller argument passed to the start() and pull() methods; see below.

For an example of how to set up a readable byte stream, including using the different controller interface, see § 10.3 A readable byte stream with an underlying push source (no backpressure support).

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

autoAllocateChunkSize (byte streams only), of type unsigned long long

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 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 § 10.3 A readable byte stream with an underlying push source (no backpressure support) without auto-allocation to § 10.5 A readable byte stream with an underlying pull source with auto-allocation.

The type of the controller argument passed to the start() and pull() methods depends on the value of the type option. If type is set to undefined (including via omission), then controller will be a ReadableStreamDefaultController. If it’s set to "bytes", then controller will be a ReadableByteStreamController.

4.2.4. Constructor, methods, and properties

stream = new ReadableStream(underlyingSource[, strategy])

Creates a new ReadableStream wrapping the provided underlying source. See § 4.2.3 The underlying source API for more details on the underlyingSource argument.

The strategy argument represents the stream’s queuing strategy, as described in § 7.1 The queuing strategy 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)

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

Returns whether or not the readable stream is locked to a reader.

await stream.cancel([ reason ])

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

reader = stream.getReader()

Creates a ReadableStreamDefaultReader and locks the stream to the new reader. While the stream is locked, no other reader can be acquired until this one is released.

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

reader = stream.getReader({ mode: "byob" })

Creates a ReadableStreamBYOBReader and 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 read() method, into developer-supplied buffers, allowing more precise control over allocation.

readable = stream.pipeThrough({ writable, readable }[, { preventClose, preventAbort, preventCancel, 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 lock it for the duration of the pipe, preventing any other consumer from acquiring a reader.

await stream.pipeTo(destination[, { preventClose, preventAbort, preventCancel, signal }])

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 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 destination, unless preventAbort is truthy. The returned promise will be rejected with the source’s error, or with any error that occurs during aborting the destination.

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

  • When this source readable stream closes, destination will be closed, unless 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 canceled, unless preventCancel is true. The returned promise will be rejected with an error indicating piping to a closed stream failed, or with any error that occurs during canceling the source.

The signal option can be set to an AbortSignal to allow aborting an ongoing pipe operation via the corresponding AbortController. In this case, this source readable stream will be canceled, and destination aborted, unless the respective options preventCancel or preventAbort are set.

[branch1, branch2] = stream.tee()

Tees this readable stream, returning a two-element array containing the two resulting branches as new ReadableStream instances.

Teeing a stream will lock it, preventing any other consumer from acquiring a reader. To cancel the stream, cancel both of the resulting branches; a composite cancellation reason will then be propagated to the stream’s underlying source.

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.

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

  3. Perform ! InitializeReadableStream(this).

  4. If underlyingSourceDict["type"] is "bytes":

    1. If strategy["size"] exists, throw a RangeError exception.

    2. Let highWaterMark be ? ExtractHighWaterMark(strategy, 0).

    3. Perform ? SetUpReadableByteStreamControllerFromUnderlyingSource(this, underlyingSource, underlyingSourceDict, highWaterMark).

  5. Otherwise,

    1. Assert: underlyingSourceDict["type"] does not exist.

    2. Let sizeAlgorithm be ! ExtractSizeAlgorithm(strategy).

    3. Let highWaterMark be ? ExtractHighWaterMark(strategy, 1).

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

  2. Return ! ReadableStreamCancel(this, reason).

The getReader(options) method steps are:
  1. If options["mode"] does not exist, return ? AcquireReadableStreamDefaultReader(this).

  2. Assert: options["mode"] is "byob".

  3. 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 canceling the stream.

The pipeThrough(transform, options) method steps are:
  1. If ! IsReadableStreamLocked(this) is true, throw a TypeError exception.

  2. If ! IsWritableStreamLocked(transform["writable"]) is true, throw a TypeError exception.

  3. Let signal be options["signal"] if it exists, or undefined otherwise.

  4. Let promise be ! ReadableStreamPipeTo(this, transform["writable"], options["preventClose"], options["preventAbort"], options["preventCancel"], signal).

  5. Set promise.[[PromiseIsHandled]] to true.

  6. Return transform["readable"].

A typical example of constructing pipe chain using 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.

  2. If ! IsWritableStreamLocked(destination) is true, return a promise rejected with a TypeError exception.

  3. Let signal be options["signal"] if it exists, or undefined otherwise.

  4. Return ! ReadableStreamPipeTo(this, destination, options["preventClose"], options["preventAbort"], options["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 pipeTo(). Additionally, the impact of the preventAbort and 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));

4.2.5. Asynchronous iteration

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

Asynchronously iterates over the chunks in the stream’s internal queue.

Asynchronously iterating over the stream will 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 breaking out of the loop.

By default, calling the async iterator’s return() method will also cancel the stream. To prevent this, use the stream’s values() method, passing true for the preventCancel option.

The asynchronous iterator initialization steps for a ReadableStream, given stream, iterator, and args, are:
  1. Let reader be ? AcquireReadableStreamDefaultReader(stream).

  2. Set iterator’s reader to reader.

  3. Let preventCancel be args[0]["preventCancel"].

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

  2. Assert: reader.[[stream]] is not undefined.

  3. Let promise be a new promise.

  4. Let readRequest be a new read request with the following items:

    chunk steps, given chunk
    1. Resolve promise with chunk.

    close steps
    1. Perform ! ReadableStreamDefaultReaderRelease(reader).

    2. Resolve promise with end of iteration.

    error steps, given e
    1. Perform ! ReadableStreamDefaultReaderRelease(reader).

    2. Reject promise with e.

  5. Perform ! ReadableStreamDefaultReaderRead(this, readRequest).

  6. Return promise.

The asynchronous iterator return steps for a ReadableStream, given stream, iterator, and arg, are:
  1. Let reader be iterator’s reader.

  2. Assert: reader.[[stream]] is not undefined.

  3. Assert: reader.[[readRequests]] is empty, as the async iterator machinery guarantees that any previous calls to next() have settled before this is called.

  4. If iterator’s prevent cancel is false:

    1. Let result be ! ReadableStreamReaderGenericCancel(reader, arg).

    2. Perform ! ReadableStreamDefaultReaderRelease(reader).

    3. Return result.

  5. Perform ! ReadableStreamDefaultReaderRelease(reader).

  6. Return a promise resolved with undefined.

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

  2. Let port1 be a new MessagePort in the current Realm.

  3. Let port2 be a new MessagePort in the current Realm.

  4. Entangle port1 and port2.

  5. Let writable be a new WritableStream in the current Realm.

  6. Perform ! SetUpCrossRealmTransformWritable(writable, port1).

  7. Let promise be ! ReadableStreamPipeTo(value, writable, false, false, false).

  8. Set promise.[[PromiseIsHandled]] to true.

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

  2. Let port be deserializedRecord.[[Deserialized]].

  3. Perform ! SetUpCrossRealmTransformReadable(value, port).

4.3. The ReadableStreamGenericReader mixin

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

4.3.1. Mixin definition

The Web IDL definition for the ReadableStreamGenericReader mixin is given as follows:

interface mixin ReadableStreamGenericReader {
  readonly attribute Promise<undefined> closed;

  Promise<undefined> cancel(optional any reason);
};

4.3.2. 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 closed getter
[[stream]] A ReadableStream instance that owns this reader

4.3.3. Methods and properties

The closed getter steps are:
  1. Return this.[[closedPromise]].

The cancel(reason) method steps are:
  1. If this.[[stream]] is undefined, return a promise rejected with a TypeError exception.

  2. Return ! ReadableStreamReaderGenericCancel(this, reason).

4.4. The ReadableStreamDefaultReader class

The ReadableStreamDefaultReader class represents a default reader designed to be vended by a ReadableStream instance.

4.4.1. Interface definition

The Web IDL definition for the ReadableStreamDefaultReader class is given as follows:

[Exposed=*]
interface ReadableStreamDefaultReader {
  constructor(ReadableStream stream);

  Promise<ReadableStreamReadResult> read();
  undefined releaseLock();
};
ReadableStreamDefaultReader includes ReadableStreamGenericReader;

dictionary ReadableStreamReadResult {
  any value;
  boolean done;
};

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

4.4.3. Constructor, methods, and properties

reader = new ReadableStreamDefaultReader(stream)

This is equivalent to calling stream.getReader().

await reader.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 released before the stream finishes closing.

await reader.cancel([ reason ])

If the reader is active, behaves the same as stream.cancel(reason).

{ value, done } = await reader.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.releaseLock()

Releases the reader’s lock on the corresponding stream. After the lock is released, the reader is no longer active. If the associated stream is errored when the lock is released, the reader will appear errored in the same way from now on; otherwise, the reader will appear closed.

If the reader’s lock is released while it still has pending read requests, then the promises returned by the reader’s 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.[[stream]] is undefined, return a promise rejected with a TypeError exception.

  2. Let promise be a new promise.

  3. Let readRequest be a new read request with the following items:

    chunk steps, given chunk
    1. Resolve promise with «[ "value" → chunk, "done" → false ]».

    close steps
    1. Resolve promise with «[ "value" → undefined, "done" → true ]».

    error steps, given e
    1. Reject promise with e.

  4. Perform ! ReadableStreamDefaultReaderRead(this, readRequest).

  5. Return promise.

The releaseLock() method steps are:
  1. If this.[[stream]] is undefined, return.

  2. Perform ! ReadableStreamDefaultReaderRelease(this).

4.5. The ReadableStreamBYOBReader class

The ReadableStreamBYOBReader class represents a BYOB reader designed to be vended by a ReadableStream instance.

4.5.1. Interface definition

The Web IDL definition for the ReadableStreamBYOBReader class is given as follows:

[Exposed=*]
interface ReadableStreamBYOBReader {
  constructor(ReadableStream stream);

  Promise<ReadableStreamReadResult> read(ArrayBufferView view, optional ReadableStreamBYOBReaderReadOptions options = {});
  undefined releaseLock();
};
ReadableStreamBYOBReader includes ReadableStreamGenericReader;

dictionary ReadableStreamBYOBReaderReadOptions {
  [EnforceRange] unsigned long long min = 1;
};

4.5.2. 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 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 close steps take a chunk so that it can return the backing memory to the caller if possible. For example, byobReader.read(chunk) will fulfill with { value: newViewOnSameMemory, done: true } for closed streams. If the stream is canceled, the backing memory is discarded and byobReader.read(chunk) fulfills with the more traditional { value: undefined, done: true } instead.

4.5.3. Constructor, methods, and properties

reader = new ReadableStreamBYOBReader(stream)

This is equivalent to calling stream.getReader({ mode: "byob" }).

await reader.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 released before the stream finishes closing.

await reader.cancel([ reason ])

If the reader is active, behaves the same stream.cancel(reason).

{ value, done } = await reader.read(view[, { 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 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 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 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 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 DataViews). 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.releaseLock()

Releases the reader’s lock on the corresponding stream. After the lock is released, the reader is no longer active. If the associated stream is errored when the lock is released, the reader will appear errored in the same way from now on; otherwise, the reader will appear closed.

If the reader’s lock is released while it still has pending read requests, then the promises returned by the reader’s 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.

  2. If view.[[ViewedArrayBuffer]].[[ByteLength]] is 0, return a promise rejected with a TypeError exception.

  3. If ! IsDetachedBuffer(view.[[ViewedArrayBuffer]]) is true, return a promise rejected with a TypeError exception.

  4. If options["min"] is 0, return a promise rejected with a TypeError exception.

  5. If view has a [[TypedArrayName]] internal slot,

    1. If options["min"] > view.[[ArrayLength]], return a promise rejected with a RangeError exception.

  6. Otherwise (i.e., it is a DataView),

    1. If options["min"] > view.[[ByteLength]], return a promise rejected with a RangeError exception.

  7. If this.[[stream]] is undefined, return a promise rejected with a TypeError exception.

  8. Let promise be a new promise.

  9. Let readIntoRequest be a new read-into request with the following items:

    chunk steps, given chunk
    1. Resolve promise with «[ "value" → chunk, "done" → false ]».

    close steps, given chunk
    1. Resolve promise with «[ "value" → chunk, "done" → true ]».

    error steps, given e
    1. Reject promise with e.

  10. Perform ! ReadableStreamBYOBReaderRead(this, view, options["min"], readIntoRequest).

  11. Return promise.

The releaseLock() method steps are:
  1. If this.[[stream]] is undefined, return.

  2. Perform ! ReadableStreamBYOBReaderRelease(this).

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

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

4.6.2. 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 [[queue]] (see § 8.1 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

4.6.3. Methods and properties

desiredSize = controller.desiredSize

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

controller.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.enqueue(chunk)

Enqueues the given chunk chunk in the controlled readable stream.

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

  2. Perform ! ReadableStreamDefaultControllerClose(this).

The enqueue(chunk) method steps are:
  1. If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(this) is false, throw a TypeError exception.

  2. Perform ? ReadableStreamDefaultControllerEnqueue(this, chunk).

The error(e) method steps are:
  1. Perform ! ReadableStreamDefaultControllerError(this, e).

4.6.4. 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 § 4.9.2 Interfacing with controllers.

[[CancelSteps]](reason) implements the [[CancelSteps]] contract. It performs the following steps:
  1. Perform ! ResetQueue(this).

  2. Let result be the result of performing this.[[cancelAlgorithm]], passing reason.

  3. Perform ! ReadableStreamDefaultControllerClearAlgorithms(this).

  4. Return result.

[[PullSteps]](readRequest) implements the [[PullSteps]] contract. It performs the following steps:
  1. Let stream be this.[[stream]].

  2. If this.[[queue]] is not empty,

    1. Let chunk be ! DequeueValue(this).

    2. If this.[[closeRequested]] is true and this.[[queue]] is empty,

      1. Perform ! ReadableStreamDefaultControllerClearAlgorithms(this).

      2. Perform ! ReadableStreamClose(stream).

    3. Otherwise, perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this).

    4. Perform readRequest’s chunk steps, given chunk.

  3. Otherwise,

    1. Perform ! ReadableStreamAddReadRequest(stream, readRequest).

    2. Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this).

[[ReleaseSteps]]() implements the [[ReleaseSteps]] contract. It performs the following steps:
  1. Return.

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

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

4.7.2. 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 entries representing the stream’s internal queue of chunks
[[queueTotalSize]] The total size, in bytes, of all the chunks stored in [[queue]] (see § 8.1 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 [[queue]] and [[queueTotalSize]] slots, we do not use most of the abstract operations in § 8.1 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 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 items:

buffer

An ArrayBuffer

buffer byte length

A positive integer representing the initial byte length of buffer

byte offset

A nonnegative integer byte offset into the buffer where the underlying byte source will start writing

byte length

A positive integer number of bytes which can be written into the buffer

bytes filled

A nonnegative integer number of bytes that have been written into the buffer so far

minimum fill

A positive integer representing the minimum number of bytes that must be written into the buffer before the associated read() request may be fulfilled. By default, this equals the element size.

element size

A positive integer representing the number of bytes that can be written into the buffer at a time, using views of the type described by the view constructor

view constructor

A typed array constructor or %DataView%, which will be used for constructing a view with which to write into the buffer

reader type

Either "default" or "byob", indicating what type of readable stream reader initiated this request, or "none" if the initiating reader was released

4.7.3. Methods and properties

byobRequest = controller.byobRequest

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

desiredSize = controller.desiredSize

Returns the 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.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.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.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.[[closeRequested]] is true, throw a TypeError exception.

  2. If this.[[stream]].[[state]] is not "readable", throw a TypeError exception.

  3. Perform ? ReadableByteStreamControllerClose(this).

The enqueue(chunk) method steps are:
  1. If chunk.[[ByteLength]] is 0, throw a TypeError exception.

  2. If chunk.[[ViewedArrayBuffer]].[[ByteLength]] is 0, throw a TypeError exception.

  3. If this.[[closeRequested]] is true, throw a TypeError exception.

  4. If this.[[stream]].[[state]] is not "readable", throw a TypeError exception.

  5. Return ? ReadableByteStreamControllerEnqueue(this, chunk).

The error(e) method steps are:
  1. Perform ! ReadableByteStreamControllerError(this, e).

4.7.4. 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 § 4.9.2 Interfacing with controllers.

[[CancelSteps]](reason) implements the [[CancelSteps]] contract. It performs the following steps:
  1. Perform ! ReadableByteStreamControllerClearPendingPullIntos(this).

  2. Perform ! ResetQueue(this).

  3. Let result be the result of performing this.[[cancelAlgorithm]], passing in reason.

  4. Perform ! ReadableByteStreamControllerClearAlgorithms(this).

  5. Return result.

[[PullSteps]](readRequest) implements the [[PullSteps]] contract. It performs the following steps:
  1. Let stream be this.[[stream]].

  2. Assert: ! ReadableStreamHasDefaultReader(stream) is true.

  3. If this.[[queueTotalSize]] > 0,

    1. Assert: ! ReadableStreamGetNumReadRequests(stream) is 0.

    2. Perform ! ReadableByteStreamControllerFillReadRequestFromQueue(this, readRequest).

    3. Return.

  4. Let autoAllocateChunkSize be this.[[autoAllocateChunkSize]].

  5. If autoAllocateChunkSize is not undefined,

    1. Let buffer be Construct(%ArrayBuffer%, « autoAllocateChunkSize »).

    2. If buffer is an abrupt completion,

      1. Perform readRequest’s error steps, given buffer.[[Value]].

      2. Return.

    3. Let pullIntoDescriptor be a new pull-into descriptor with

      buffer
      buffer.[[Value]]
      buffer byte length
      autoAllocateChunkSize
      byte offset
      0
      byte length
      autoAllocateChunkSize
      bytes filled
      0
      minimum fill
      1
      element size
      1
      view constructor
      %Uint8Array%
      reader type
      "default"
    4. Append pullIntoDescriptor to this.[[pendingPullIntos]].

  6. Perform ! ReadableStreamAddReadRequest(stream, readRequest).

  7. Perform ! ReadableByteStreamControllerCallPullIfNeeded(this).

[[ReleaseSteps]]() implements the [[ReleaseSteps]] contract. It performs the following steps:
  1. If