Streams ( PR #718 )

PR Preview — Last Updated

Participate:
GitHub whatwg/streams ( new issue , open issues )
IRC: #whatwg on Freenode
GitHub whatwg/streams ( new issue , open issues )
IRC: #whatwg on Freenode
Commits:
GitHub whatwg/streams/commits
Go to the living standard
@[TWITTER]
GitHub whatwg/streams/commits
[SNAPSHOT-LINK]
@streamsstandard
Tests:
web-platform-tests streams/ ( ongoing work )
Translation (non-normative):
日本語
This is a pull request preview of the standard

This document contains the contents of the standard as modified by pull request #718 , and should only be used as a preview.

Do not attempt to implement this version of the standard. Do not reference this version as authoritative in any way. Instead, see https://streams.spec.whatwg.org/ for the living standard.

Abstract

This specification provides APIs for creating, composing, and consuming streams of data. These streams are designed to map efficiently to low-level I/O primitives, and allow easy composition with built-in backpressure and queuing. On top of streams, the web platform can build higher-level abstractions, such as filesystem or socket APIs, while at the same time users can use the supplied tools to build their own streams which integrate well with those of the web platform.

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 .

This standard provides the base stream primitives which other parts of the web platform can use to expose their streaming data. For example, [FETCH] could expose request bodies as a writable stream, or response bodies as a readable stream. More generally, the platform is full of streaming abstractions waiting to be expressed as streams: multimedia streams, file streams, interprocess 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:

The APIs described here provide unifying abstraction for all such streams, encouraging an ecosystem to grow around these shared and composable interfaces. At the same time, they have been carefully designed to map efficiently to low-level I/O concerns, and to encapsulate the trickier issues (such as backpressure ) that come along for the ride.

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 Uint8Array s, 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.

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.

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.

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. 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. 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 a underlying byte source . A readable stream whose underlying source is an underlying byte source is sometimes called a readable byte stream .

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.

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 .

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

2.3. Transform Streams

A transform stream consists of a pair of streams: a writable stream, and a readable stream. 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.

Some examples of 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, or it can be piped through one or more transform streams first.

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 can be used to 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 .

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.

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.

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.

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.

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.

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

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.

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.

3. Readable Streams

3.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);
readInto(startingAB)
  .then(buffer => console.log("The first 1024 bytes:", buffer))
  .catch(e => console.error("Something went wrong!", e));
function readInto(buffer, offset = 0) {
  if (offset === buffer.byteLength) {
    return Promise.resolve(buffer);
  }
  const view = new Uint8Array(buffer, offset, buffer.byteLength - offset);
  return reader.read(view).then(newView => {
    return readInto(newView.buffer, offset + newView.byteLength);
  });
}

An important thing to note here is that the final buffer value is different from the startingAB , but it (and all intermediate buffers) shares the same backing memory allocation. At each step, the buffer is transferred to a new ArrayBuffer object. The newView is a new Uint8Array , with that ArrayBuffer object as its buffer property, the offset that bytes were written to as its byteOffset property, and the number of bytes that were written as its byteLength property.

3.2. Class ReadableStream

ReadableStream

In all current engines.

Firefox 65+ Safari 10.1+ Chrome 43+
Opera 30+ Edge 79+
Edge (Legacy) 14+ IE None
Firefox for Android 65+ iOS Safari 10.3+ Chrome for Android 43+ Android WebView 43+ Samsung Internet 4.0+ Opera Mobile 30+

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.

3.2.1. Class Definition

This section is non-normative.

If one were to write the ReadableStream class in something close to the syntax of [ECMASCRIPT] , it would look like

class ReadableStream {
  constructor(underlyingSource = {}, { size, highWaterMark } = {})
  get locked()
  cancel(reason)
  getReader()
  pipeThrough({ writable, readable }, options)
  pipeTo(dest, { preventClose, preventAbort, preventCancel } = {})
  tee()
}

3.2.2. Internal Slots

Instances of ReadableStream are created with the internal slots described in the following table:

Internal Slot Description ( non-normative )
[[disturbed]] A boolean flag set to true when the stream has been read from or canceled
[[readableStreamController]] A ReadableStreamDefaultController or ReadableByteStreamController created with the ability to control the state and queue of this stream; also used for the IsReadableStream brand check
[[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

3.2.3. new ReadableStream( underlyingSource = {}, { size , highWaterMark } = {})

ReadableStream/ReadableStream

In all current engines.

Firefox 65+ Safari 10.1+ Chrome 43+
Opera 30+ Edge 79+
Edge (Legacy) None IE None
Firefox for Android 65+ iOS Safari 10.3+ Chrome for Android 43+ Android WebView 43+ Samsung Internet 4.0+ Opera Mobile 30+
The underlyingSource object passed to the constructor can implement any of the following methods to govern how the constructed stream instance behaves:

Both start and pull are given the ability to manipulate the stream’s internal queue and state via the passed controller object. This is an example of the revealing constructor pattern .

If the underlyingSource object contains a property type set to "bytes" , this readable stream is a readable byte stream , and can successfully vend BYOB readers . In that case, the passed controller object will be an instance of ReadableByteStreamController . Otherwise, it will be an instance of ReadableStreamDefaultController .

For readable byte streams , underlyingSource can also contain a property autoAllocateChunkSize , which can be set to a positive integer to enable the auto-allocation feature for this stream. In that case, when a consumer uses a default reader , the stream implementation will automatically allocate an ArrayBuffer of the given size, and call the underlying source code as if the consumer was using a BYOB reader . This can cut down on the amount of code needed when writing the underlying source implementation, as can be seen by comparing § 8.3 A readable byte stream with an underlying push source (no backpressure support) without auto-allocation to § 8.5 A readable byte stream with an underlying pull source with auto-allocation.

The constructor also accepts a second argument containing the queuing strategy object with two properties: a non-negative number highWaterMark , and a function size(chunk) . The supplied strategy could be an instance of the built-in CountQueuingStrategy or ByteLengthQueuingStrategy classes, or it could be custom. If no strategy is supplied, the default behavior will be the same as a CountQueuingStrategy with a high water mark of 1.

1. Set *this*.[[state]] to `"readable"`. 1. Set *this*.[[reader]] and *this*.[[storedError]] to *undefined*. 1. Set *this*.[[disturbed]] to *false*. 1. Set *this*.[[readableStreamController]] to *undefined*. 1. Let _type_ be ? GetV(_underlyingSource_, `"type"`). 1. Let _typeString_ be ? ToString(_type_). 1. If _typeString_ is `"bytes"`, 1. If _highWaterMark_ is *undefined*, let _highWaterMark_ be *0*. 1. Set *this*.[[readableStreamController]] to ? Construct(` ReadableByteStreamController `, « *this*, _underlyingSource_, _highWaterMark_ »). 1. Otherwise, if _type_ is *undefined*, 1. If _highWaterMark_ is *undefined*, let _highWaterMark_ be *1*. 1. Set *this*.[[readableStreamController]] to ? Construct(` ReadableStreamDefaultController `, « *this*, _underlyingSource_, _size_, _highWaterMark_ »). 1. Otherwise, throw a *RangeError* exception.

3.2.4. Properties of the ReadableStream Prototype

3.2.4.1. get locked

ReadableStream/locked

In all current engines.

Firefox 65+ Safari 10.1+ Chrome 43+
Opera 30+ Edge 79+
Edge (Legacy) 14+ IE None
Firefox for Android 65+ iOS Safari 10.3+ Chrome for Android 43+ Android WebView 43+ Samsung Internet 4.0+ Opera Mobile 30+
The locked getter returns whether or not the readable stream is locked to a reader .
1. If ! IsReadableStream(*this*) is *false*, throw a *TypeError* exception. 1. Return ! IsReadableStreamLocked(*this*).
3.2.4.2. cancel( reason )

ReadableStream/cancel

In all current engines.

Firefox 65+ Safari 10.1+ Chrome 43+
Opera 30+ Edge 79+
Edge (Legacy) 14+ IE None
Firefox for Android 65+ iOS Safari 10.3+ Chrome for Android 43+ Android WebView 43+ Samsung Internet 4.0+ Opera Mobile 30+
The cancel method cancels the stream, signaling a loss of interest in the stream by a consumer. The supplied reason argument will be given to the underlying source, which may or may not use it.
1. If ! IsReadableStream(*this*) is *false*, return a promise rejected with a *TypeError* exception. 1. If ! IsReadableStreamLocked(*this*) is *true*, return a promise rejected with a *TypeError* exception. 1. Return ! ReadableStreamCancel(*this*, _reason_).
3.2.4.3. getReader({ mode } = {})

ReadableStream/getReader

In all current engines.

Firefox 65+ Safari 10.1+ Chrome 43+
Opera 30+ Edge 79+
Edge (Legacy) 14+ IE None
Firefox for Android 65+ iOS Safari 10.3+ Chrome for Android 43+ Android WebView 43+ Samsung Internet 4.0+ Opera Mobile 30+
The getReader method creates a reader of the type specified by the mode option and locks the stream to the new reader. While the stream is locked, no other reader can be acquired until this one is released .

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

When mode is undefined , the method creates a default reader (an instance of ReadableStreamDefaultReader ). The reader provides the ability to directly read individual chunks from the stream via the reader’s read() method.

When mode is "byob" , the getReader method creates a BYOB reader (an instance of ReadableStreamBYOBReader ). This feature only works on readable byte streams , i.e. streams which were constructed specifically with the ability to handle "bring your own buffer" reading. The reader provides the ability to directly read individual chunks from the stream via the reader’s read() method, into developer-supplied buffers, allowing more precise control over allocation.

1. If ! IsReadableStream(*this*) is *false*, throw a *TypeError* exception. 1. If _mode_ is `"byob"`, return ? AcquireReadableStreamBYOBReader(*this*). 1. If _mode_ is *undefined*, return ? AcquireReadableStreamDefaultReader(*this*). 1. Throw a *RangeError* exception.
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.

3.2.4.4. pipeThrough({ writable , readable }, options )

ReadableStream/pipeThrough

Firefox None Safari 10.1+ Chrome 59+
Opera 46+ Edge 79+
Edge (Legacy) None IE None
Firefox for Android None iOS Safari 10.3+ Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 43+
The pipeThrough method 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 generally lock it for the duration of the pipe, preventing any other consumer from acquiring a reader.

This method is intentionally generic; it does not require that its this value be a ReadableStream object. It also does not require that its writable argument be a WritableStream instance, or that its readable argument be a ReadableStream instance.

1. Let _promise_ be ? Invoke(*this*, `"pipeTo"`, « _writable_, _options_ »). 1. If Type(_promise_) is Object and _promise_ has a [[PromiseIsHandled]] internal slot, set _promise_.[[PromiseIsHandled]] to *true*. 1. Return _readable_.
A typical example of constructing pipe chain using pipeThrough(transform, options) would look like
httpResponseBody
  .pipeThrough(decompressorTransform)
  .pipeThrough(ignoreNonImageFilesTransform)
  .pipeTo(mediaGallery);

3.2.4.5. pipeTo( dest , { preventClose , preventAbort , preventCancel } = {})

ReadableStream/pipeTo

Firefox None Safari 10.1+ Chrome 59+
Opera 46+ Edge 79+
Edge (Legacy) None IE None
Firefox for Android None iOS Safari 10.3+ Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 43+
The pipeTo method pipes this readable stream to a given writable stream . 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:

1. If ! IsReadableStream(*this*) is *false*, return a promise rejected with a *TypeError* exception. 1. If ! IsWritableStream(_dest_) is *false*, return a promise rejected with a *TypeError* exception. 1. Set _preventClose_ to ! ToBoolean(_preventClose_), set _preventAbort_ to ! ToBoolean(_preventAbort_), and set _preventCancel_ to ! ToBoolean(_preventCancel_). 1. If ! IsReadableStreamLocked(*this*) is *true*, return a promise rejected with a *TypeError* exception. 1. If ! IsWritableStreamLocked(_dest_) is *true*, return a promise rejected with a *TypeError* exception. 1. If ! IsReadableByteStreamController(*this*.[[readableStreamController]]) is *true*, let _reader_ be either ! AcquireReadableStreamBYOBReader(*this*) or ! AcquireReadableStreamDefaultReader(*this*), at the user agent’s discretion. 1. Otherwise, let _reader_ be ! AcquireReadableStreamDefaultReader(*this*). 1. Let _writer_ be ! AcquireWritableStreamDefaultWriter(_dest_). 1. Let _shuttingDown_ be *false*. 1. Let _promise_ be a new promise . 1. In parallel , using _reader_ and _writer_, read all chunks from *this* 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 to determine the size of the chunks read from _reader_. * Otherwise, WritableStreamDefaultWriterGetDesiredSize(_writer_) may be used to determine the flow rate heuristically, e.g. by delaying reads while it is judged to be "low" compared to the size of chunks that have been typically read. * Shutdown must stop all activity: if _shuttingDown_ becomes *true*, the user agent must not initiate further reads from _reader_ or writes to _writer_. (Ongoing reads and writes may finish.) In particular, the user agent must check the below conditions on *this*.[[state]] and _dest_.[[state]] before performing any reads or writes, since they might lead to immediate shutdown. * Errors must be propagated forward: if *this*.[[state]] is or becomes `"errored"`, then 1. If _preventAbort_ is *false*, shutdown with an action of ! WritableStreamAbort(_dest_, *this*.[[storedError]]) and with *this*.[[storedError]]. 1. Otherwise, shutdown with *this*.[[storedError]]. * Errors must be propagated backward: if _dest_.[[state]] is or becomes `"errored"`, then 1. If _preventCancel_ is *false*, shutdown with an action of ! ReadableStreamCancel(*this*, _dest_.[[storedError]]) and with _dest_.[[storedError]]. 1. Otherwise, shutdown with _dest_.[[storedError]]. * Closing must be propagated forward: if *this*.[[state]] is or becomes `"closed"`, then 1. If _preventClose_ is *false*, shutdown with an action of ! WritableStreamDefaultWriterCloseWithErrorPropagation(_writer_). 1. Otherwise, shutdown . * Closing must be propagated backward: if ! WritableStreamCloseQueuedOrInFlight(_dest_) is *true* or _dest_.[[state]] is `"closed"`, then 1. Let _destClosed_ be a new *TypeError*. 1. If _preventCancel_ is *false*, shutdown with an action of ! ReadableStreamCancel(*this*, _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. Wait until any ongoing write finishes (i.e. the corresponding promises settle). 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. Wait until any ongoing write finishes (i.e. the corresponding promises settle). 1. Finalize , passing along _error_ if it was given. * Finalize : both forms of shutdown will eventually ask to finalize, optionally with an error _error_, which means to perform the following steps: 1. Perform ! WritableStreamDefaultWriterRelease(_writer_). 1. Perform ! ReadableStreamReaderGenericRelease(_reader_). 1. If _error_ was given, reject _promise_ with _error_. 1. Otherwise, resolve _promise_ with *undefined*. 1. Return _promise_.
3.2.4.6. tee()

ReadableStream/tee

In all current engines.

Firefox 65+ Safari 10.1+ Chrome 43+
Opera 30+ Edge 79+
Edge (Legacy) None IE None
Firefox for Android 65+ iOS Safari 10.3+ Chrome for Android 43+ Android WebView 43+ Samsung Internet 4.0+ Opera Mobile 30+
The tee method tees this readable stream, returning a two-element array containing the two resulting branches as new ReadableStream instances.

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

Note that the chunks seen in each branch will be the same object. If the chunks are not immutable, this could allow interference between the two branches. ( Let us know if you think we should add an option to tee that creates structured clones of the chunks for each branch.)

1. If ! IsReadableStream(*this*) is *false*, throw a *TypeError* exception. 1. Let _branches_ be ? ReadableStreamTee(*this*, *false*). 1. Return ! CreateArrayFromList(_branches_).
Teeing a stream is most useful when you wish to let two independent consumers read from the stream in parallel, perhaps even at different speeds. For example, given a writable stream cacheEntry representing an on-disk file, and another writable stream httpRequestBody representing an upload to a remote server, you could pipe the same readable stream to both destinations at once:
const [forLocal, forRemote] = readableStream.tee();
Promise.all([
  forLocal.pipeTo(cacheEntry),
  forRemote.pipeTo(httpRequestBody)
])
.then(() => console.log("Saved the stream to the cache and also uploaded it!"))
.catch(e => console.error("Either caching or uploading failed: ", e));

3.3. General Readable Stream Abstract Operations

The following abstract operations, unlike most in this specification, are meant to be generally useful by other specifications, instead of just being part of the implementation of this spec’s classes.

3.3.1. AcquireReadableStreamBYOBReader ( stream )

This abstract operation is meant to be called from other specifications that may wish to acquire a BYOB reader for a given stream.

1. Return ? Construct(` ReadableStreamBYOBReader `, « _stream_ »).

3.3.2. AcquireReadableStreamDefaultReader ( stream )

This abstract operation is meant to be called from other specifications that may wish to acquire a default reader for a given stream.

1. Return ? Construct(` ReadableStreamDefaultReader `, « _stream_ »).

3.3.3. IsReadableStream ( x )

1. If Type(_x_) is not Object, return *false*. 1. If _x_ does not have a [[readableStreamController]] internal slot, return *false*. 1. Return *true*.

3.3.4. IsReadableStreamDisturbed ( stream )

This abstract operation is meant to be called from other specifications that may wish to query whether or not a readable stream has ever been read from or canceled.

1. Assert: ! IsReadableStream(_stream_) is *true*. 1. Return _stream_.[[disturbed]].

3.3.5. IsReadableStreamLocked ( stream )

This abstract operation is meant to be called from other specifications that may wish to query whether or not a readable stream is locked to a reader .

1. Assert: ! IsReadableStream(_stream_) is *true*. 1. If _stream_.[[reader]] is *undefined*, return *false*. 1. Return *true*.

3.3.6. ReadableStreamTee ( stream , cloneForBranch2 )

This abstract operation is meant to be called from other specifications that may wish to tee a given readable stream.

The second argument, cloneForBranch2 , governs whether or not the data from the original stream will be structured cloned 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 transfering their chunks . However, it does introduce a noticable asymmetry between the two branches. [HTML]

1. Assert: ! IsReadableStream(_stream_) is *true*. 1. Assert: Type(_cloneForBranch2_) is Boolean. 1. Let _reader_ be ? AcquireReadableStreamDefaultReader(_stream_). 1. Let _teeState_ be Record {[[closedOrErrored]]: *false*, [[canceled1]]: *false*, [[canceled2]]: *false*, [[reason1]]: *undefined*, [[reason2]]: *undefined*, [[promise]]: a new promise }. 1. Let _pull_ be a new ReadableStreamTee pull function . 1. Set _pull_.[[reader]] to _reader_, _pull_.[[teeState]] to _teeState_, and _pull_.[[cloneForBranch2]] to _cloneForBranch2_. 1. Let _cancel1_ be a new ReadableStreamTee branch 1 cancel function . 1. Set _cancel1_.[[stream]] to _stream_ and _cancel1_.[[teeState]] to _teeState_. 1. Let _cancel2_ be a new ReadableStreamTee branch 2 cancel function . 1. Set _cancel2_.[[stream]] to _stream_ and _cancel2_.[[teeState]] to _teeState_. 1. Let _underlyingSource1_ be ! ObjectCreate(%ObjectPrototype%). 1. Perform ! CreateDataProperty(_underlyingSource1_, `"pull"`, _pull_). 1. Perform ! CreateDataProperty(_underlyingSource1_, `"cancel"`, _cancel1_). 1. Let _branch1Stream_ be ! Construct(` ReadableStream `, _underlyingSource1_). 1. Let _underlyingSource2_ be ! ObjectCreate(%ObjectPrototype%). 1. Perform ! CreateDataProperty(_underlyingSource2_, `"pull"`, _pull_). 1. Perform ! CreateDataProperty(_underlyingSource2_, `"cancel"`, _cancel2_). 1. Let _branch2Stream_ be ! Construct(` ReadableStream `, _underlyingSource2_). 1. Set _pull_.[[branch1]] to _branch1Stream_.[[readableStreamController]]. 1. Set _pull_.[[branch2]] to _branch2Stream_.[[readableStreamController]]. 1. Upon rejection of _reader_.[[closedPromise]] with reason _r_, 1. If _teeState_.[[closedOrErrored]] is *false*, then: 1. Perform ! ReadableStreamDefaultControllerError(_pull_.[[branch1]], _r_). 1. Perform ! ReadableStreamDefaultControllerError(_pull_.[[branch2]], _r_). 1. Set _teeState_.[[closedOrErrored]] to *true*. 1. Return « _branch1Stream_, _branch2Stream_ ».

A ReadableStreamTee pull function is an anonymous built-in function that pulls data from a given readable stream reader and enqueues it into two other streams ("branches" of the associated tee). Each ReadableStreamTee pull function has [[reader]], [[branch1]], [[branch2]], [[teeState]], and [[cloneForBranch2]] internal slots. When a ReadableStreamTee pull function F is called, it performs the following steps:

1. Let _reader_ be _F_.[[reader]], _branch1_ be _F_.[[branch1]], _branch2_ be _F_.[[branch2]], _teeState_ be _F_.[[teeState]], and _cloneForBranch2_ be _F_.[[cloneForBranch2]]. 1. Return the result of transforming ! ReadableStreamDefaultReaderRead(_reader_) with a fulfillment handler which takes the argument _result_ and performs the following steps: 1. Assert: Type(_result_) is Object. 1. Let _value_ be ? Get(_result_, `"value"`). 1. Let _done_ be ? Get(_result_, `"done"`). 1. Assert: Type(_done_) is Boolean. 1. If _done_ is *true* and _teeState_.[[closedOrErrored]] is *false*, 1. If _teeState_.[[canceled1]] is *false*, 1. Perform ! ReadableStreamDefaultControllerClose(_branch1_). 1. If _teeState_.[[canceled2]] is *false*, 1. Perform ! ReadableStreamDefaultControllerClose(_branch2_). 1. Set _teeState_.[[closedOrErrored]] to *true*. 1. If _teeState_.[[closedOrErrored]] is *true*, return. 1. Let _value1_ and _value2_ be _value_. 1. If _teeState_.[[canceled2]] is *false* and _cloneForBranch2_ is *true*, set _value2_ to ? StructuredClone (_value2_). 1. If _teeState_.[[canceled1]] is *false*, perform ? ReadableStreamDefaultControllerEnqueue(_branch1_, _value1_). 1. If _teeState_.[[canceled2]] is *false*, perform ? ReadableStreamDefaultControllerEnqueue(_branch2_, _value2_).

A ReadableStreamTee branch 1 cancel function is an anonymous built-in function that reacts to the cancellation of the first of the two branches of the associated tee. Each ReadableStreamTee branch 1 cancel function has [[stream]] and [[teeState]] internal slots. When a ReadableStreamTee branch 1 cancel function F is called with argument reason , it performs the following steps:

1. Let _stream_ be _F_.[[stream]] and _teeState_ be _F_.[[teeState]]. 1. Set _teeState_.[[canceled1]] to *true*. 1. Set _teeState_.[[reason1]] to _reason_. 1. If _teeState_.[[canceled2]] is *true*, 1. Let _compositeReason_ be ! CreateArrayFromList(« _teeState_.[[reason1]], _teeState_.[[reason2]] »). 1. Let _cancelResult_ be ! ReadableStreamCancel(_stream_, _compositeReason_). 1. Resolve _teeState_.[[promise]] with _cancelResult_. 1. Return _teeState_.[[promise]].

A ReadableStreamTee branch 2 cancel function is an anonymous built-in function that reacts to the cancellation of the second of the two branches of the associated tee. Each ReadableStreamTee branch 2 cancel function has [[stream]] and [[teeState]] internal slots. When a ReadableStreamTee branch 2 cancel function F is called with argument reason , it performs the following steps:

1. Let _stream_ be _F_.[[stream]] and _teeState_ be _F_.[[teeState]]. 1. Set _teeState_.[[canceled2]] to *true*. 1. Set _teeState_.[[reason2]] to _reason_. 1. If _teeState_.[[canceled1]] is *true*, 1. Let _compositeReason_ be ! CreateArrayFromList(« _teeState_.[[reason1]], _teeState_.[[reason2]] »). 1. Let _cancelResult_ be ! ReadableStreamCancel(_stream_, _compositeReason_). 1. Resolve _teeState_.[[promise]] with _cancelResult_. 1. Return _teeState_.[[promise]].
The algorithm given here is written such that three new function objects are created for each call to to ReadableStreamTee. This is just a simplification, and is not actually necessary, since it is unobservable to developer code. For example, a self-hosted implementation could optimize by creating a class whose prototype contains methods for these functions, with the state stored as instance variables.

3.4. Readable Stream Abstract Operations Used by 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 .

The abstract operations in this section are interfaces that are used by the controller implementations to affect their associated ReadableStream object, translating those internal state changes into developer-facing results visible through the ReadableStream 's public API.

3.4.1. ReadableStreamAddReadIntoRequest ( stream )

1. Assert: ! IsReadableStreamBYOBReader(_stream_.[[reader]]) is *true*. 1. Assert: _stream_.[[state]] is `"readable"` or `"closed"`. 1. Let _promise_ be a new promise . 1. Let _readIntoRequest_ be Record {[[promise]]: _promise_}. 1. Append _readIntoRequest_ as the last element of _stream_.[[reader]].[[readIntoRequests]]. 1. Return _promise_.

3.4.2. ReadableStreamAddReadRequest ( stream )

1. Assert: ! IsReadableStreamDefaultReader(_stream_.[[reader]]) is *true*. 1. Assert: _stream_.[[state]] is `"readable"`. 1. Let _promise_ be a new promise . 1. Let _readRequest_ be Record {[[promise]]: _promise_}. 1. Append _readRequest_ as the last element of _stream_.[[reader]].[[readRequests]]. 1. Return _promise_.

3.4.3. ReadableStreamCancel ( stream , reason )

1. Set _stream_.[[disturbed]] to *true*. 1. If _stream_.[[state]] is `"closed"`, return a promise resolved with *undefined*. 1. If _stream_.[[state]] is `"errored"`, return a promise rejected with _stream_.[[storedError]]. 1. Perform ! ReadableStreamClose(_stream_). 1. Let _sourceCancelPromise_ be ! _stream_.[[readableStreamController]].[[CancelSteps]](_reason_). 1. Return the result of transforming _sourceCancelPromise_ with a fulfillment handler that returns *undefined*.

3.4.4. ReadableStreamClose ( stream )

1. Assert: _stream_.[[state]] is `"readable"`. 1. Set _stream_.[[state]] to `"closed"`. 1. Let _reader_ be _stream_.[[reader]]. 1. If _reader_ is *undefined*, return. 1. If ! IsReadableStreamDefaultReader(_reader_) is *true*, 1. Repeat for each _readRequest_ that is an element of _reader_.[[readRequests]], 1. Resolve _readRequest_.[[promise]] with ! CreateIterResultObject(*undefined*, *true*). 1. Set _reader_.[[readRequests]] to an empty List. 1. Resolve _reader_.[[closedPromise]] with *undefined*.
The case where stream .[[state]] is "closed" , but stream .[[closeRequested]] is false , will happen if the stream was closed without its controller’s close method ever being called: i.e., if the stream was closed by a call to cancel(reason) . In this case we allow the controller’s close method to be called and silently do nothing, since the cancelation was outside the control of the underlying source.

3.4.5. ReadableStreamError ( stream , e )

1. Assert: ! IsReadableStream(_stream_) is *true*. 1. Assert: _stream_.[[state]] is `"readable"`. 1. Set _stream_.[[state]] to `"errored"`. 1. Set _stream_.[[storedError]] to _e_. 1. Let _reader_ be _stream_.[[reader]]. 1. If _reader_ is *undefined*, return. 1. If ! IsReadableStreamDefaultReader(_reader_) is *true*, 1. Repeat for each _readRequest_ that is an element of _reader_.[[readRequests]], 1. Reject _readRequest_.[[promise]] with _e_. 1. Set _reader_.[[readRequests]] to a new empty List. 1. Otherwise, 1. Assert: ! IsReadableStreamBYOBReader(_reader_). 1. Repeat for each _readIntoRequest_ that is an element of _reader_.[[readIntoRequests]], 1. Reject _readIntoRequest_.[[promise]] with _e_. 1. Set _reader_.[[readIntoRequests]] to a new empty List. 1. Reject _reader_.[[closedPromise]] with _e_. 1. Set _reader_.[[closedPromise]].[[PromiseIsHandled]] to *true*.

3.4.6. ReadableStreamFulfillReadIntoRequest ( stream , chunk , done )

1. Let _reader_ be _stream_.[[reader]]. 1. Let _readIntoRequest_ be the first element of _reader_.[[readIntoRequests]]. 1. Remove _readIntoRequest_ from _reader_.[[readIntoRequests]], shifting all other elements downward (so that the second becomes the first, and so on). 1. Resolve _readIntoRequest_.[[promise]] with ! CreateIterResultObject(_chunk_, _done_).

3.4.7. ReadableStreamFulfillReadRequest ( stream , chunk , done )

1. Let _reader_ be _stream_.[[reader]]. 1. Let _readRequest_ be the first element of _reader_.[[readRequests]]. 1. Remove _readRequest_ from _reader_.[[readRequests]], shifting all other elements downward (so that the second becomes the first, and so on). 1. Resolve _readRequest_.[[promise]] with ! CreateIterResultObject(_chunk_, _done_).

3.4.8. ReadableStreamGetNumReadIntoRequests ( stream )

1. Return the number of elements in _stream_.[[reader]].[[readIntoRequests]].

3.4.9. ReadableStreamGetNumReadRequests ( stream )

1. Return the number of elements in _stream_.[[reader]].[[readRequests]].

3.4.10. ReadableStreamHasBYOBReader ( stream )

1. Let _reader_ be _stream_.[[reader]]. 1. If _reader_ is *undefined*, return *false*. 1. If ! IsReadableStreamBYOBReader(_reader_) is *false*, return *false*. 1. Return *true*.

3.4.11. ReadableStreamHasDefaultReader ( stream )

1. Let _reader_ be _stream_.[[reader]]. 1. If _reader_ is *undefined*, return *false*. 1. If ! IsReadableStreamDefaultReader(_reader_) is *false*, return *false*. 1. Return *true*.

3.5. Class ReadableStreamDefaultReader

ReadableStreamDefaultReader

Firefox 65+ Safari ? Chrome 52+
Opera 39+ Edge 79+
Edge (Legacy) None IE None
Firefox for Android 65+ iOS Safari ? Chrome for Android 52+ Android WebView 52+ Samsung Internet 6.0+ Opera Mobile 41+

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

3.5.1. Class Definition

This section is non-normative.

If one were to write the ReadableStreamDefaultReader class in something close to the syntax of [ECMASCRIPT] , it would look like

class ReadableStreamDefaultReader {
  constructor(stream)
  get closed()
  cancel(reason)
  read()
  releaseLock()
}

3.5.2. Internal Slots

Instances of ReadableStreamDefaultReader 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
[[ownerReadableStream]] A ReadableStream instance that owns this reader
[[readRequests]] A List of promises returned by calls to the reader’s read() method that have not yet been resolved, due to the consumer requesting chunks sooner than they are available; also used for the IsReadableStreamDefaultReader brand check

3.5.3. new ReadableStreamDefaultReader( stream )

ReadableStreamDefaultReader/ReadableStreamDefaultReader

In only one current engine.

Firefox 65+ Safari ? Chrome ?
Opera ? Edge ?
Edge (Legacy) ? IE None
Firefox for Android 65+ iOS Safari ? Chrome for Android ? Android WebView ? Samsung Internet ? Opera Mobile ?
The ReadableStreamDefaultReader constructor is generally not meant to be used directly; instead, a stream’s getReader() method should be used.
1. If ! IsReadableStream(_stream_) is *false*, throw a *TypeError* exception. 1. If ! IsReadableStreamLocked(_stream_) is *true*, throw a *TypeError* exception. 1. Perform ! ReadableStreamReaderGenericInitialize(*this*, _stream_). 1. Set *this*.[[readRequests]] to a new empty List.

3.5.4. Properties of the ReadableStreamDefaultReader Prototype

3.5.4.1. get closed

ReadableStreamDefaultReader/closed

In only one current engine.

Firefox 65+ Safari ? Chrome ?
Opera ? Edge ?
Edge (Legacy) ? IE None
Firefox for Android 65+ iOS Safari ? Chrome for Android ? Android WebView ? Samsung Internet ? Opera Mobile ?
The closed getter returns a promise that will be fulfilled when the stream becomes closed or the reader’s lock is released , or rejected if the stream ever errors.
1. If ! IsReadableStreamDefaultReader(*this*) is *false*, return a promise rejected with a *TypeError* exception. 1. Return *this*.[[closedPromise]].
3.5.4.2. cancel( reason )

ReadableStreamDefaultReader/cancel

In only one current engine.

Firefox 65+ Safari ? Chrome ?
Opera ? Edge ?
Edge (Legacy) ? IE None
Firefox for Android 65+ iOS Safari ? Chrome for Android ? Android WebView ? Samsung Internet ? Opera Mobile ?
If the reader is active , the cancel method behaves the same as that for the associated stream.
1. If ! IsReadableStreamDefaultReader(*this*) is *false*, return a promise rejected with a *TypeError* exception. 1. If *this*.[[ownerReadableStream]] is *undefined*, return a promise rejected with a *TypeError* exception. 1. Return ! ReadableStreamReaderGenericCancel(*this*, _reason_).
3.5.4.3. read()

ReadableStreamDefaultReader/read

In only one current engine.

Firefox 65+ Safari ? Chrome ?
Opera ? Edge ?
Edge (Legacy) ? IE None
Firefox for Android 65+ iOS Safari ? Chrome for Android ? Android WebView ? Samsung Internet ? Opera Mobile ?
The read method will return a promise that allows access to the next chunk from the stream’s internal queue, if available.

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

1. If ! IsReadableStreamDefaultReader(*this*) is *false*, return a promise rejected with a *TypeError* exception. 1. If *this*.[[ownerReadableStream]] is *undefined*, return a promise rejected with a *TypeError* exception. 1. Return ! ReadableStreamDefaultReaderRead(*this*).
3.5.4.4. releaseLock()

ReadableStreamDefaultReader/releaseLock

In only one current engine.

Firefox 65+ Safari ? Chrome ?
Opera ? Edge ?
Edge (Legacy) ? IE None
Firefox for Android 65+ iOS Safari ? Chrome for Android ? Android WebView ? Samsung Internet ? Opera Mobile ?

WritableStreamDefaultWriter/releaseLock

In only one current engine.

Firefox None Safari None Chrome 59+
Opera 46+ Edge 79+
Edge (Legacy) 16+ IE None
Firefox for Android None iOS Safari None Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 43+
The releaseLock method releases the reader’s lock on the corresponding stream. After the lock is released, the reader is no longer active . If the associated stream is errored when the lock is released, the reader will appear errored in the same way from now on; otherwise, the reader will appear closed.

A reader’s lock cannot be released while it still has a pending read request, i.e., if a promise returned by the reader’s read() method has not yet been settled. Attempting to do so will throw a TypeError and leave the reader locked to the stream.

1. If ! IsReadableStreamDefaultReader(*this*) is *false*, throw a *TypeError* exception. 1. If *this*.[[ownerReadableStream]] is *undefined*, return. 1. If *this*.[[readRequests]] is not empty, throw a *TypeError* exception. 1. Perform ! ReadableStreamReaderGenericRelease(*this*).

3.6. Class ReadableStreamBYOBReader

ReadableStreamBYOBReader

In no current engines.

Firefox None Safari None Chrome None
Opera None Edge None
Edge (Legacy) None IE None
Firefox for Android None iOS Safari None Chrome for Android None Android WebView None Samsung Internet None Opera Mobile None

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

3.6.1. Class Definition

This section is non-normative.

If one were to write the ReadableStreamBYOBReader class in something close to the syntax of [ECMASCRIPT] , it would look like

class ReadableStreamBYOBReader {
  constructor(stream)
  get closed()
  cancel(reason)
  read(view)
  releaseLock()
}

3.6.2. Internal Slots

Instances of ReadableStreamBYOBReader 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
[[ownerReadableStream]] A ReadableStream instance that owns this reader
[[readIntoRequests]] A List of promises returned by calls to the reader’s read(view) method that have not yet been resolved, due to the consumer requesting chunks sooner than they are available; also used for the IsReadableStreamBYOBReader brand check

3.6.3. new ReadableStreamBYOBReader( stream )

ReadableStreamBYOBReader/ReadableStreamBYOBReader

In no current engines.

Firefox None Safari None Chrome None
Opera None Edge None
Edge (Legacy) None IE None
Firefox for Android None iOS Safari None Chrome for Android None Android WebView None Samsung Internet None Opera Mobile None
The ReadableStreamBYOBReader constructor is generally not meant to be used directly; instead, a stream’s getReader() method should be used.
1. If ! IsReadableStream(_stream_) is *false*, throw a *TypeError* exception. 1. If ! IsReadableByteStreamController(_stream_.[[readableStreamController]]) is *false*, throw a *TypeError* exception. 1. If ! IsReadableStreamLocked(_stream_) is *true*, throw a *TypeError* exception. 1. Perform ! ReadableStreamReaderGenericInitialize(*this*, _stream_). 1. Set *this*.[[readIntoRequests]] to a new empty List.

3.6.4. Properties of the ReadableStreamBYOBReader Prototype

3.6.4.1. get closed

ReadableStreamBYOBReader/closed

In no current engines.

Firefox None Safari None Chrome None
Opera None Edge None
Edge (Legacy) None IE None
Firefox for Android None iOS Safari None Chrome for Android None Android WebView None Samsung Internet None Opera Mobile None
The closed getter returns a promise that will be fulfilled when the stream becomes closed or the reader’s lock is released , or rejected if the stream ever errors.
1. If ! IsReadableStreamBYOBReader(*this*) is *false*, return a promise rejected with a *TypeError* exception. 1. Return *this*.[[closedPromise]].
3.6.4.2. cancel( reason )

ReadableStreamBYOBReader/cancel

In no current engines.

Firefox None Safari None Chrome None
Opera None Edge None
Edge (Legacy) None IE None
Firefox for Android None iOS Safari None Chrome for Android None Android WebView None Samsung Internet None Opera Mobile None
If the reader is active , the cancel method behaves the same as that for the associated stream.
1. If ! IsReadableStreamBYOBReader(*this*) is *false*, return a promise rejected with a *TypeError* exception. 1. If *this*.[[ownerReadableStream]] is *undefined*, return a promise rejected with a *TypeError* exception. 1. Return ! ReadableStreamReaderGenericCancel(*this*, _reason_).
3.6.4.3. read( view )

ReadableStreamBYOBReader/read

In no current engines.

Firefox None Safari None Chrome None
Opera None Edge None
Edge (Legacy) None IE None
Firefox for Android None iOS Safari None Chrome for Android None Android WebView None Samsung Internet None Opera Mobile None
The read method will write read bytes into view and return a promise resolved with a possibly transferred buffer as described below.

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

1. If ! IsReadableStreamBYOBReader(*this*) is *false*, return a promise rejected with a *TypeError* exception. 1. If *this*.[[ownerReadableStream]] is *undefined*, return a promise rejected with a *TypeError* exception. 1. If Type(_view_) is not Object, return a promise rejected with a *TypeError* exception. 1. If _view_ does not have a [[ViewedArrayBuffer]] internal slot, return a promise rejected with a *TypeError* exception. 1. If _view_.[[ByteLength]] is *0*, return a promise rejected with a *TypeError* exception. 1. Return ! ReadableStreamBYOBReaderRead(*this*, _view_).
3.6.4.4. releaseLock()

ReadableStreamBYOBReader/releaseLock

In no current engines.

Firefox None Safari None Chrome None
Opera None Edge None
Edge (Legacy) None IE None
Firefox for Android None iOS Safari None Chrome for Android None Android WebView None Samsung Internet None Opera Mobile None
The releaseLock method releases the reader’s lock on the corresponding stream. After the lock is released, the reader is no longer active . If the associated stream is errored when the lock is released, the reader will appear errored in the same way from now on; otherwise, the reader will appear closed.

A reader’s lock cannot be released while it still has a pending read request, i.e., if a promise returned by the reader’s read() method has not yet been settled. Attempting to do so will throw a TypeError and leave the reader locked to the stream.

1. If ! IsReadableStreamBYOBReader(*this*) is *false*, throw a *TypeError* exception. 1. If *this*.[[ownerReadableStream]] is *undefined*, return. 1. If *this*.[[readIntoRequests]] is not empty, throw a *TypeError* exception. 1. Perform ! ReadableStreamReaderGenericRelease(*this*).

3.7. Readable Stream Reader Abstract Operations

3.7.1. IsReadableStreamDefaultReader ( x )

1. If Type(_x_) is not Object, return *false*. 1. If _x_ does not have a [[readRequests]] internal slot, return *false*. 1. Return *true*.

3.7.2. IsReadableStreamBYOBReader ( x )

1. If Type(_x_) is not Object, return *false*. 1. If _x_ does not have a [[readIntoRequests]] internal slot, return *false*. 1. Return *true*.

3.7.3. ReadableStreamReaderGenericCancel ( reader , reason )

1. Let _stream_ be _reader_.[[ownerReadableStream]]. 1. Assert: _stream_ is not *undefined*. 1. Return ! ReadableStreamCancel(_stream_, _reason_).

3.7.4. ReadableStreamReaderGenericInitialize ( reader , stream )

1. Set _reader_.[[ownerReadableStream]] to _stream_. 1. Set _stream_.[[reader]] to _reader_. 1. If _stream_.[[state]] is `"readable"`, 1. Set _reader_.[[closedPromise]] to a new promise . 1. Otherwise, if _stream_.[[state]] is `"closed"`, 1. Set _reader_.[[closedPromise]] to a promise resolved with *undefined*. 1. Otherwise, 1. Assert: _stream_.[[state]] is `"errored"`. 1. Set _reader_.[[closedPromise]] to a promise rejected with _stream_.[[storedError]]. 1. Set _reader_.[[closedPromise]].[[PromiseIsHandled]] to *true*.

3.7.5. ReadableStreamReaderGenericRelease ( reader )

1. Assert: _reader_.[[ownerReadableStream]] is not *undefined*. 1. Assert: _reader_.[[ownerReadableStream]].[[reader]] is _reader_. 1. If _reader_.[[ownerReadableStream]].[[state]] is `"readable"`, reject _reader_.[[closedPromise]] with a *TypeError* exception. 1. Otherwise, set _reader_.[[closedPromise]] to a promise rejected with a *TypeError* exception. 1. Set _reader_.[[closedPromise]].[[PromiseIsHandled]] to *true*. 1. Set _reader_.[[ownerReadableStream]].[[reader]] to *undefined*. 1. Set _reader_.[[ownerReadableStream]] to *undefined*.

3.7.6. ReadableStreamBYOBReaderRead ( reader , view )

1. Let _stream_ be _reader_.[[ownerReadableStream]]. 1. Assert: _stream_ is not *undefined*. 1. Set _stream_.[[disturbed]] to *true*. 1. If _stream_.[[state]] is `"errored"`, return a promise rejected with _stream_.[[storedError]]. 1. Return ! ReadableByteStreamControllerPullInto(_stream_.[[readableStreamController]], _view_).

3.7.7. ReadableStreamDefaultReaderRead ( reader )

1. Let _stream_ be _reader_.[[ownerReadableStream]]. 1. Assert: _stream_ is not *undefined*. 1. Set _stream_.[[disturbed]] to *true*. 1. If _stream_.[[state]] is `"closed"`, return a promise resolved with ! CreateIterResultObject(*undefined*, *true*). 1. If _stream_.[[state]] is `"errored"`, return a promise rejected with _stream_.[[storedError]]. 1. Assert: _stream_.[[state]] is `"readable"`. 1. Return ! _stream_.[[readableStreamController]].[[PullSteps]]().

3.8. Class ReadableStreamDefaultController

ReadableStreamDefaultController

Firefox 65+ Safari ? Chrome 52+
Opera 39+ Edge 79+
Edge (Legacy) None IE None
Firefox for Android 65+ iOS Safari ? Chrome for Android 52+ Android WebView 52+ Samsung Internet 6.0+ Opera Mobile 41+

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.

3.8.1. Class Definition

This section is non-normative.

If one were to write the ReadableStreamDefaultController class in something close to the syntax of [ECMASCRIPT] , it would look like

class ReadableStreamDefaultController {
  constructor(stream, underlyingSource, size, highWaterMark)
  get desiredSize()
  close()
  enqueue(chunk)
  error(e)
}

3.8.2. Internal Slots

Instances of ReadableStreamDefaultController are created with the internal slots described in the following table:

Internal Slot Description ( non-normative )
[[closeRequested]] A boolean flag indicating whether the stream has been closed by its underlying source , but still has chunks in its internal queue that have not yet been read
[[controlledReadableStream]] The ReadableStream instance controlled
[[pullAgain]] A boolean flag set to true if the stream’s mechanisms requested a call to the underlying source’s pull method to pull more data, but the pull could not yet be done since a previous call is still executing
[[pulling]] A boolean flag set to true while the underlying source ’s pull method is executing and 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 § 6.3 Queue-with-Sizes Operations )
[[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
[[strategySize]] A function supplied to the constructor as part of the stream’s queuing strategy , designed to calculate the size of enqueued chunks ; can be undefined for the default behavior
[[underlyingSource]] An object representation of the stream’s underlying source ; also used for the IsReadableStreamDefaultController brand check

3.8.3. new ReadableStreamDefaultController( stream , underlyingSource , size , highWaterMark )

The ReadableStreamDefaultController constructor cannot be used directly; it only works on a ReadableStream that is in the middle of being constructed.
1. If ! IsReadableStream(_stream_) is *false*, throw a *TypeError* exception. 1. If _stream_.[[readableStreamController]] is not *undefined*, throw a *TypeError* exception. 1. Set *this*.[[controlledReadableStream]] to _stream_. 1. Set *this*.[[underlyingSource]] to _underlyingSource_. 1. Perform ! ResetQueue(*this*). 1. Set *this*.[[started]], *this*.[[closeRequested]], *this*.[[pullAgain]], and *this*.[[pulling]] to *false*. 1. Let _normalizedStrategy_ be ? ValidateAndNormalizeQueuingStrategy(_size_, _highWaterMark_). 1. Set *this*.[[strategySize]] to _normalizedStrategy_.[[size]] and *this*.[[strategyHWM]] to _normalizedStrategy_.[[highWaterMark]]. 1. Let _controller_ be *this*. 1. Let _startResult_ be ? InvokeOrNoop(_underlyingSource_, `"start"`, « *this* »). 1. Let _startPromise_ be a promise resolved with _startResult_: 1. Upon fulfillment of _startPromise_, 1. Set _controller_.[[started]] to *true*. 1. Assert: _controller_.[[pulling]] is *false*. 1. Assert: _controller_.[[pullAgain]] is *false*. 1. Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(_controller_). 1. Upon rejection of _startPromise_ with reason _r_, 1. Perform ! ReadableStreamDefaultControllerErrorIfNeeded(_controller_, _r_).

3.8.4. Properties of the ReadableStreamDefaultController Prototype

3.8.4.1. get desiredSize

ReadableStreamDefaultController/desiredSize

In only one current engine.

Firefox 65+ Safari ? Chrome ?
Opera ? Edge ?
Edge (Legacy) ? IE None
Firefox for Android 65+ iOS Safari ? Chrome for Android ? Android WebView ? Samsung Internet ? Opera Mobile ?
The desiredSize getter returns the desired size to fill the controlled stream’s internal queue . It can be negative, if the queue is over-full. An underlying source should use this information to determine when and how to apply backpressure .
1. If ! IsReadableStreamDefaultController(*this*) is *false*, throw a *TypeError* exception. 1. Return ! ReadableStreamDefaultControllerGetDesiredSize(*this*).
3.8.4.2. close()

ReadableStreamDefaultController/close

In only one current engine.

Firefox 65+ Safari ? Chrome ?
Opera ? Edge ?
Edge (Legacy) ? IE None
Firefox for Android 65+ iOS Safari ? Chrome for Android ? Android WebView ? Samsung Internet ? Opera Mobile ?
The close method will close the controlled readable stream. Consumers will still be able to read any previously-enqueued chunks from the stream, but once those are read, the stream will become closed.
1. If ! IsReadableStreamDefaultController(*this*) is *false*, throw a *TypeError* exception. 1. If *this*.[[closeRequested]] is *true*, throw a *TypeError* exception. 1. If *this*.[[controlledReadableStream]].[[state]] is not `"readable"`, throw a *TypeError* exception. 1. Perform ! ReadableStreamDefaultControllerClose(*this*).
3.8.4.3. enqueue( chunk )

ReadableStreamDefaultController/enqueue

In only one current engine.

Firefox 65+ Safari ? Chrome ?
Opera ? Edge ?
Edge (Legacy) ? IE None
Firefox for Android 65+ iOS Safari ? Chrome for Android ? Android WebView ? Samsung Internet ? Opera Mobile ?
The enqueue method will enqueue a given chunk in the controlled readable stream.
1. If ! IsReadableStreamDefaultController(*this*) is *false*, throw a *TypeError* exception. 1. If *this*.[[closeRequested]] is *true*, throw a *TypeError* exception. 1. If *this*.[[controlledReadableStream]].[[state]] is not `"readable"`, throw a *TypeError* exception. 1. Return ? ReadableStreamDefaultControllerEnqueue(*this*, _chunk_).
3.8.4.4. error( e )

ReadableStreamDefaultController/error

In only one current engine.

Firefox 65+ Safari ? Chrome ?
Opera ? Edge ?
Edge (Legacy) ? IE None
Firefox for Android 65+ iOS Safari ? Chrome for Android ? Android WebView ? Samsung Internet ? Opera Mobile ?
The error method will error the readable stream, making all future interactions with it fail with the given error e .
1. If ! IsReadableStreamDefaultController(*this*) is *false*, throw a *TypeError* exception. 1. Let _stream_ be *this*.[[controlledReadableStream]]. 1. If _stream_.[[state]] is not `"readable"`, throw a *TypeError* exception. 1. Perform ! ReadableStreamDefaultControllerError(*this*, _e_).

3.8.5. Readable Stream Default Controller Internal Methods

The following are additional internal methods implemented by each ReadableStreamDefaultController instance. The readable stream implementation will polymorphically call to either these or their counterparts for BYOB controllers.

3.8.5.1. [[CancelSteps]]( reason )
1. Perform ! ResetQueue(*this*). 1. Return ! PromiseInvokeOrNoop(*this*.[[underlyingSource]], `"cancel"`, « _reason_ »)
3.8.5.2. [[PullSteps]]()
1. Let _stream_ be *this*.[[controlledReadableStream]]. 1. If *this*.[[queue]] is not empty, 1. Let _chunk_ be ! DequeueValue(*this*). 1. If *this*.[[closeRequested]] is *true* and *this*.[[queue]] is empty, perform ! ReadableStreamClose(_stream_). 1. Otherwise, perform ! ReadableStreamDefaultControllerCallPullIfNeeded(*this*). 1. Return a promise resolved with ! CreateIterResultObject(_chunk_, *false*). 1. Let _pendingPromise_ be ! ReadableStreamAddReadRequest(_stream_). 1. Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(*this*). 1. Return _pendingPromise_.

3.9. Readable Stream Default Controller Abstract Operations

3.9.1. IsReadableStreamDefaultController ( x )

1. If Type(_x_) is not Object, return *false*. 1. If _x_ does not have an [[underlyingSource]] internal slot, return *false*. 1. Return *true*.

3.9.2. ReadableStreamDefaultControllerCallPullIfNeeded ( controller )

1. Let _shouldPull_ be ! ReadableStreamDefaultControllerShouldCallPull(_controller_). 1. If _shouldPull_ is *false*, return. 1. If _controller_.[[pulling]] is *true*, 1. Set _controller_.[[pullAgain]] to *true*. 1. Return. 1. Assert: _controller_.[[pullAgain]] is *false*. 1. Set _controller_.[[pulling]] to *true*. 1. Let _pullPromise_ be ! PromiseInvokeOrNoop(_controller_.[[underlyingSource]], `"pull"`, « _controller_ »). 1. Upon fulfillment of _pullPromise_, 1. Set _controller_.[[pulling]] to *false*. 1. If _controller_.[[pullAgain]] is *true*, 1. Set _controller_.[[pullAgain]] to *false*. 1. Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(_controller_). 1. Upon rejection of _pullPromise_ with reason _e_, 1. Perform ! ReadableStreamDefaultControllerErrorIfNeeded(_controller_, _e_).

3.9.3. ReadableStreamDefaultControllerShouldCallPull ( controller )

1. Let _stream_ be _controller_.[[controlledReadableStream]]. 1. If _stream_.[[state]] is `"closed"` or _stream_.[[state]] is `"errored"`, return *false*. 1. If _controller_.[[closeRequested]] is *true*, return *false*. 1. If _controller_.[[started]] is *false*, return *false*. 1. If ! IsReadableStreamLocked(_stream_) is *true* and ! ReadableStreamGetNumReadRequests(_stream_) > *0*, return *true*. 1. Let _desiredSize_ be ReadableStreamDefaultControllerGetDesiredSize(_controller_). 1. If _desiredSize_ > *0*, return *true*. 1. Return *false*.

3.9.4. ReadableStreamDefaultControllerClose ( controller )

This abstract operation can be called by other specifications that wish to close a readable stream, in the same way a developer-created stream would be closed by its associated controller object. Specifications should not do this to streams they did not create, and must ensure they have obeyed the preconditions (listed here as asserts).

1. Let _stream_ be _controller_.[[controlledReadableStream]]. 1. Assert: _controller_.[[closeRequested]] is *false*. 1. Assert: _stream_.[[state]] is `"readable"`. 1. Set _controller_.[[closeRequested]] to *true*. 1. If _controller_.[[queue]] is empty, perform ! ReadableStreamClose(_stream_).

3.9.5. ReadableStreamDefaultControllerEnqueue ( controller , chunk )

This abstract operation can be called by other specifications that wish to enqueue chunks in a readable stream, in the same way a developer would enqueue chunks using the stream’s associated controller object. Specifications should not do this to streams they did not create, and must ensure they have obeyed the preconditions (listed here as asserts).

1. Let _stream_ be _controller_.[[controlledReadableStream]]. 1. Assert: _controller_.[[closeRequested]] is *false*. 1. Assert: _stream_.[[state]] is `"readable"`. 1. If ! IsReadableStreamLocked(_stream_) is *true* and ! ReadableStreamGetNumReadRequests(_stream_) > *0*, perform ! ReadableStreamFulfillReadRequest(_stream_, _chunk_, *false*). 1. Otherwise, 1. Let _chunkSize_ be *1*. 1. If _controller_.[[strategySize]] is not *undefined*, 1. Set _chunkSize_ to Call(_controller_.[[strategySize]], *undefined*, « _chunk_ »). 1. If _chunkSize_ is an abrupt completion, 1. Perform ! ReadableStreamDefaultControllerErrorIfNeeded(_controller_, _chunkSize_.[[Value]]). 1. Return _chunkSize_. 1. Let _chunkSize_ be _chunkSize_.[[Value]]. 1. Let _enqueueResult_ be ! EnqueueValueWithSize(_controller_, _chunk_, _chunkSize_). 1. If _enqueueResult_ is an abrupt completion, 1. Perform ! ReadableStreamDefaultControllerErrorIfNeeded(_controller_, _enqueueResult_.[[Value]]). 1. Return _enqueueResult_. 1. Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(_controller_).
The case where stream .[[state]] is "closed" , but stream .[[closeRequested]] is false , will happen if the stream was closed without its controller’s close method ever being called: i.e., if the stream was closed by a call to cancel(reason) . In this case we allow the controller’s enqueue method to be called and silently do nothing, since the cancelation was outside the control of the underlying source.

3.9.6. ReadableStreamDefaultControllerError ( controller , e )

This abstract operation can be called by other specifications that wish to move a readable stream to an errored state, in the same way a developer would error a stream using its associated controller object. Specifications should not do this to streams they did not create, and must ensure they have obeyed the precondition (listed here as an assert).

1. Let _stream_ be _controller_.[[controlledReadableStream]]. 1. Assert: _stream_.[[state]] is `"readable"`. 1. Perform ! ResetQueue(_controller_). 1. Perform ! ReadableStreamError(_stream_, _e_).

3.9.7. ReadableStreamDefaultControllerErrorIfNeeded ( controller , e )

1. If _controller_.[[controlledReadableStream]].[[state]] is `"readable"`, perform ! ReadableStreamDefaultControllerError(_controller_, _e_).

3.9.8. ReadableStreamDefaultControllerGetDesiredSize ( controller )

This abstract operation can be called by other specifications that wish to determine the desired size to fill this stream’s internal queue , similar to how a developer would consult the desiredSize property of the stream’s associated controller object. Specifications should not use this on streams they did not create.

1. Let _stream_ be _controller_.[[controlledReadableStream]]. 1. Let _state_ be _stream_.[[state]]. 1. If _state_ is `"errored"`, return *null*. 1. If _state_ is `"closed"`, return *0*. 1. Return _controller_.[[strategyHWM]] − _controller_.[[queueTotalSize]].

3.10. Class ReadableByteStreamController

ReadableByteStreamController

In no current engines.

Firefox None Safari None Chrome None
Opera None Edge None
Edge (Legacy) None IE None
Firefox for Android None iOS Safari None Chrome for Android None Android WebView None Samsung Internet None Opera Mobile None

The ReadableByteStreamController class has methods that allow control of a ReadableStream 's state and internal queue . When constructing a ReadableStream , the underlying byte source is given a corresponding ReadableByteStreamController instance to manipulate.

3.10.1. Class Definition

This section is non-normative.

If one were to write the ReadableByteStreamController class in something close to the syntax of [ECMASCRIPT] , it would look like

class ReadableByteStreamController {
  constructor(stream, underlyingByteSource, highWaterMark)
  get byobRequest()
  get desiredSize()
  close()
  enqueue(chunk)
  error(e)
}

3.10.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.
[[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
[[controlledReadableStream]] The ReadableStream instance controlled
[[pullAgain]] A boolean flag set to true if the stream’s mechanisms requested a call to the underlying byte source ’s pull method to pull more data, but the pull could not yet be done since a previous call is still executing
[[pulling]] A boolean flag set to true while the underlying byte source ’s pull method is executing and has not yet fulfilled, used to prevent reentrant calls
[[byobRequest]] A ReadableStreamBYOBRequest instance representing the current BYOB pull request
[[pendingPullIntos]] A List of descriptors representing pending BYOB pull requests
[[queue]] A List representing the stream’s internal queue of chunks
[[queueTotalSize]] The total size (in bytes) of all the chunks stored in [[queue]]
[[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 byte source
[[underlyingByteSource]] An object representation of the stream’s underlying byte source ; also used for the IsReadableByteStreamController brand check

Although ReadableByteStreamController instances have [[queue]] and [[queueTotalSize]] slots, we do not use most of the abstract operations in § 6.3 Queue-with-Sizes Operations 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.

3.10.3. new ReadableByteStreamController( stream , underlyingByteSource , highWaterMark )

The ReadableByteStreamController constructor cannot be used directly; it only works on a ReadableStream that is in the middle of being constructed.
1. If ! IsReadableStream(_stream_) is *false*, throw a *TypeError* exception. 1. If _stream_.[[readableStreamController]] is not *undefined*, throw a *TypeError* exception. 1. Set *this*.[[controlledReadableStream]] to _stream_. 1. Set *this*.[[underlyingByteSource]] to _underlyingByteSource_. 1. Set *this*.[[pullAgain]], and *this*.[[pulling]] to *false*. 1. Perform ! ReadableByteStreamControllerClearPendingPullIntos(*this*). 1. Perform ! ResetQueue(*this*). 1. Set *this*.[[started]] and *this*.[[closeRequested]] to *false*. 1. Set *this*.[[strategyHWM]] to ? ValidateAndNormalizeHighWaterMark(_highWaterMark_). 1. Let _autoAllocateChunkSize_ be ? GetV(_underlyingByteSource_, `"autoAllocateChunkSize"`). 1. If _autoAllocateChunkSize_ is not *undefined*, 1. If ! IsInteger(_autoAllocateChunkSize_) is *false*, or if _autoAllocateChunkSize_ ≤ *0*, throw a *RangeError* exception. 1. Set *this*.[[autoAllocateChunkSize]] to _autoAllocateChunkSize_. 1. Set *this*.[[pendingPullIntos]] to a new empty List. 1. Let _controller_ be *this*. 1. Let _startResult_ be ? InvokeOrNoop(_underlyingByteSource_, `"start"`, « *this* »). 1. Let _startPromise_ be a promise resolved with _startResult_: 1. Upon fulfillment of _startPromise_, 1. Set _controller_.[[started]] to *true*. 1. Assert: _controller_.[[pulling]] is *false*. 1. Assert: _controller_.[[pullAgain]] is *false*. 1. Perform ! ReadableByteStreamControllerCallPullIfNeeded(_controller_). 1. Upon rejection of _startPromise_ with reason _r_, 1. If _stream_.[[state]] is `"readable"`, perform ! ReadableByteStreamControllerError(_controller_, _r_).

3.10.4. Properties of the ReadableByteStreamController Prototype

3.10.4.1. get byobRequest

ReadableByteStreamController/byobRequest

In no current engines.

Firefox None Safari None Chrome None
Opera None Edge None
Edge (Legacy) None IE None
Firefox for Android None iOS Safari None Chrome for Android None Android WebView None Samsung Internet None Opera Mobile None
The byobRequest getter returns the current BYOB pull request.
1. If IsReadableByteStreamController(*this*) is *false*, throw a *TypeError* exception. 1. If *this*.[[byobRequest]] is *undefined* and *this*.[[pendingPullIntos]] is not empty, 1. Let _firstDescriptor_ be the first element of *this*.[[pendingPullIntos]]. 1. Let _view_ be ! Construct( %Uint8Array% , « _firstDescriptor_.[[buffer]], _firstDescriptor_.[[byteOffset]] + _firstDescriptor_.[[bytesFilled]], _firstDescriptor_.[[byteLength]] − _firstDescriptor_.[[bytesFilled]] »). 1. Set *this*.[[byobRequest]] to ! Construct(` ReadableStreamBYOBRequest `, « *this*, _view_ »). 1. Return *this*.[[byobRequest]].
3.10.4.2. get desiredSize

ReadableByteStreamController/desiredSize

In no current engines.

Firefox None Safari None Chrome None
Opera None Edge None
Edge (Legacy) None IE None
Firefox for Android None iOS Safari None Chrome for Android None Android WebView None Samsung Internet None Opera Mobile None
The desiredSize getter returns the desired size to fill the controlled stream’s internal queue . It can be negative, if the queue is over-full. An underlying source should use this information to determine when and how to apply backpressure .
1. If ! IsReadableByteStreamController(*this*) is *false*, throw a *TypeError* exception. 1. Return ! ReadableByteStreamControllerGetDesiredSize(*this*).
3.10.4.3. close()

ReadableByteStreamController/close

In no current engines.

Firefox None Safari None Chrome None
Opera None Edge None
Edge (Legacy) None IE None
Firefox for Android None iOS Safari None Chrome for Android None Android WebView None Samsung Internet None Opera Mobile None
The close method will close the controlled readable stream. Consumers will still be able to read any previously-enqueued chunks from the stream, but once those are read, the stream will become closed.
1. If ! IsReadableByteStreamController(*this*) is *false*, throw a *TypeError* exception. 1. If *this*.[[closeRequested]] is *true*, throw a *TypeError* exception. 1. If *this*.[[controlledReadableStream]].[[state]] is not `"readable"`, throw a *TypeError* exception. 1. Perform ? ReadableByteStreamControllerClose(*this*).
3.10.4.4. enqueue( chunk )

ReadableByteStreamController/enqueue

In no current engines.

Firefox None Safari None Chrome None
Opera None Edge None
Edge (Legacy) None IE None
Firefox for Android None iOS Safari None Chrome for Android None Android WebView None Samsung Internet None Opera Mobile None
The enqueue method will enqueue a given chunk in the controlled readable stream.
1. If ! IsReadableByteStreamController(*this*) is *false*, throw a *TypeError* exception. 1. If *this*.[[closeRequested]] is *true*, throw a *TypeError* exception. 1. If *this*.[[controlledReadableStream]].[[state]] is not `"readable"`, throw a *TypeError* exception. 1. If Type(_chunk_) is not Object, throw a *TypeError* exception. 1. If _chunk_ does not have a [[ViewedArrayBuffer]] internal slot, throw a *TypeError* exception. 1. Return ! ReadableByteStreamControllerEnqueue(*this*, _chunk_).
3.10.4.5. error( e )

ReadableByteStreamController/error

In no current engines.

Firefox None Safari None Chrome None
Opera None Edge None
Edge (Legacy) None IE None
Firefox for Android None iOS Safari None Chrome for Android None Android WebView None Samsung Internet None Opera Mobile None
The error method will error the readable stream, making all future interactions with it fail with the given error e .
1. If ! IsReadableByteStreamController(*this*) is *false*, throw a *TypeError* exception. 1. Let _stream_ be *this*.[[controlledReadableStream]]. 1. If _stream_.[[state]] is not `"readable"`, throw a *TypeError* exception. 1. Perform ! ReadableByteStreamControllerError(*this*, _e_).

3.10.5. Readable Stream BYOB Controller Internal Methods

The following are additional internal methods implemented by each ReadableByteStreamController instance. The readable stream implementation will polymorphically call to either these or their counterparts for default controllers.

3.10.5.1. [[CancelSteps]]( reason )
1. If *this*.[[pendingPullIntos]] is not empty, 1. Let _firstDescriptor_ be the first element of *this*.[[pendingPullIntos]]. 1. Set _firstDescriptor_.[[bytesFilled]] to *0*. 1. Perform ! ResetQueue(*this*). 1. Return ! PromiseInvokeOrNoop(*this*.[[underlyingByteSource]], `"cancel"`, « _reason_ »)
3.10.5.2. [[PullSteps]]()
1. Let _stream_ be *this*.[[controlledReadableStream]]. 1. Assert: ! ReadableStreamHasDefaultReader(_stream_) is *true*. 1. If *this*.[[queueTotalSize]] > *0*, 1. Assert: ! ReadableStreamGetNumReadRequests(_stream_) is *0*. 1. Let _entry_ be the first element of *this*.[[queue]]. 1. Remove _entry_ from *this*.[[queue]], shifting all other elements downward (so that the second becomes the first, and so on). 1. Set *this*.[[queueTotalSize]] to *this*.[[queueTotalSize]] − _entry_.[[byteLength]]. 1. Perform ! ReadableByteStreamControllerHandleQueueDrain(*this*). 1. Let _view_ be ! Construct( %Uint8Array% , « _entry_.[[buffer]], _entry_.[[byteOffset]], _entry_.[[byteLength]] »). 1. Return a promise resolved with ! CreateIterResultObject(_view_, *false*). 1. Let _autoAllocateChunkSize_ be *this*.[[autoAllocateChunkSize]]. 1. If _autoAllocateChunkSize_ is not *undefined*, 1. Let _buffer_ be Construct(%ArrayBuffer%, « _autoAllocateChunkSize_ »). 1. If _buffer_ is an abrupt completion, return a promise rejected with _buffer_.[[Value]]. 1. Let _pullIntoDescriptor_ be Record {[[buffer]]: _buffer_.[[Value]], [[byteOffset]]: *0*, [[byteLength]]: _autoAllocateChunkSize_, [[bytesFilled]]: *0*, [[elementSize]]: *1*, [[ctor]]: %Uint8Array% , [[readerType]]: `"default"`}. 1. Append _pullIntoDescriptor_ as the last element of *this*.[[pendingPullIntos]]. 1. Let _promise_ be ! ReadableStreamAddReadRequest(_stream_). 1. Perform ! ReadableByteStreamControllerCallPullIfNeeded(*this*). 1. Return _promise_.

3.11. Class ReadableStreamBYOBRequest

ReadableStreamBYOBRequest

In no current engines.

Firefox None Safari None Chrome None
Opera None Edge None
Edge (Legacy) None IE None
Firefox for Android None iOS Safari None Chrome for Android None Android WebView None Samsung Internet None Opera Mobile None

The ReadableStreamBYOBRequest class represents a pull into request in a ReadableByteStreamController .

3.11.1. Class Definition

This section is non-normative.

If one were to write the ReadableStreamBYOBRequest class in something close to the syntax of [ECMASCRIPT] , it would look like

class ReadableStreamBYOBRequest {
  constructor(controller, view)
  get view()
  respond(bytesWritten)
  respondWithNewView(view)
}

3.11.2. Internal Slots

Instances of ReadableStreamBYOBRequest are created with the internal slots described in the following table:

Internal Slot Description ( non-normative )
[[associatedReadableByteStreamController]] The parent ReadableByteStreamController instance
[[view]] A typed array representing the destination region to which the controller may write generated data

3.11.3. new ReadableStreamBYOBRequest( controller , view )

1. Set *this*.[[associatedReadableByteStreamController]] to _controller_. 1. Set *this*.[[view]] to _view_.

3.11.4. Properties of the ReadableStreamBYOBRequest Prototype

3.11.4.1. get view

ReadableStreamBYOBRequest/view

In no current engines.

Firefox None Safari None Chrome None
Opera None Edge None
Edge (Legacy) None IE None
Firefox for Android None iOS Safari None Chrome for Android None Android WebView None Samsung Internet None Opera Mobile None
1. If ! IsReadableStreamBYOBRequest(*this*) is *false*, throw a *TypeError* exception. 1. Return *this*.[[view]].
3.11.4.2. respond( bytesWritten )

ReadableStreamBYOBRequest/respond

In no current engines.

Firefox None Safari None Chrome None
Opera None Edge None
Edge (Legacy) None IE None
Firefox for Android None iOS Safari None Chrome for Android None Android WebView None Samsung Internet None Opera Mobile None
1. If ! IsReadableStreamBYOBRequest(*this*) is *false*, throw a *TypeError* exception. 1. If *this*.[[associatedReadableByteStreamController]] is *undefined*, throw a *TypeError* exception. 1. Return ? ReadableByteStreamControllerRespond(*this*.[[associatedReadableByteStreamController]], _bytesWritten_).
3.11.4.3. respondWithNewView( view )

ReadableStreamBYOBRequest/respondWithNewView

In no current engines.

Firefox None Safari None Chrome None
Opera None Edge None
Edge (Legacy) None IE None
Firefox for Android None iOS Safari None Chrome for Android None Android WebView None Samsung Internet None Opera Mobile None
1. If ! IsReadableStreamBYOBRequest(*this*) is *false*, throw a *TypeError* exception. 1. If *this*.[[associatedReadableByteStreamController]] is *undefined*, throw a *TypeError* exception. 1. If Type(_view_) is not Object, throw a *TypeError* exception. 1. If _view_ does not have a [[ViewedArrayBuffer]] internal slot, throw a *TypeError* exception. 1. Return ? ReadableByteStreamControllerRespondWithNewView(*this*.[[associatedReadableByteStreamController]], _view_).

3.12. Readable Stream BYOB Controller Abstract Operations

3.12.1. IsReadableStreamBYOBRequest ( x )

1. If Type(_x_) is not Object, return *false*. 1. If _x_ does not have an [[associatedReadableByteStreamController]] internal slot, return *false*. 1. Return *true*.

3.12.2. IsReadableByteStreamController ( x )

1. If Type(_x_) is not Object, return *false*. 1. If _x_ does not have an [[underlyingByteSource]] internal slot, return *false*. 1. Return *true*.

3.12.3. ReadableByteStreamControllerCallPullIfNeeded ( controller )

1. Let _shouldPull_ be ! ReadableByteStreamControllerShouldCallPull(_controller_). 1. If _shouldPull_ is *false*, return. 1. If _controller_.[[pulling]] is *true*, 1. Set _controller_.[[pullAgain]] to *true*. 1. Return. 1. Assert: _controller_.[[pullAgain]] is *false*. 1. Set _controller_.[[pulling]] to *true*. 1. Let _pullPromise_ be ! PromiseInvokeOrNoop(_controller_.[[underlyingByteSource]], `"pull"`, « _controller_ »). 1. Upon fulfillment of _pullPromise_, 1. Set _controller_.[[pulling]] to *false*. 1. If _controller_.[[pullAgain]] is *true*, 1. Set _controller_.[[pullAgain]] to *false*. 1. Perform ! ReadableByteStreamControllerCallPullIfNeeded(_controller_). 1. Upon rejection of _pullPromise_ with reason _e_, 1. If _controller_.[[controlledReadableStream]].[[state]] is `"readable"`, perform ! ReadableByteStreamControllerError(_controller_, _e_).

3.12.4. ReadableByteStreamControllerClearPendingPullIntos ( controller )

1. Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(_controller_). 1. Set _controller_.[[pendingPullIntos]] to a new empty List.

3.12.5. ReadableByteStreamControllerClose ( controller )

1. Let _stream_ be _controller_.[[controlledReadableStream]]. 1. Assert: _controller_.[[closeRequested]] is *false*. 1. Assert: _stream_.[[state]] is `"readable"`. 1. If _controller_.[[queueTotalSize]] > *0*, 1. Set _controller_.[[closeRequested]] to *true*. 1. Return. 1. If _controller_.[[pendingPullIntos]] is not empty, 1. Let _firstPendingPullInto_ be the first element of _controller_.[[pendingPullIntos]]. 1. If _firstPendingPullInto_.[[bytesFilled]] > *0*, 1. Let _e_ be a new *TypeError* exception. 1. Perform ! ReadableByteStreamControllerError(_controller_, _e_). 1. Throw _e_. 1. Perform ! ReadableStreamClose(_stream_).

3.12.6. ReadableByteStreamControllerCommitPullIntoDescriptor ( stream , pullIntoDescriptor )

1. Assert: _stream_.[[state]] is not `"errored"`. 1. Let _done_ be *false*. 1. If _stream_.[[state]] is `"closed"`, 1. Assert: _pullIntoDescriptor_.[[bytesFilled]] is *0*. 1. Set _done_ to *true*. 1. Let _filledView_ be ! ReadableByteStreamControllerConvertPullIntoDescriptor(_pullIntoDescriptor_). 1. If _pullIntoDescriptor_.[[readerType]] is `"default"`, 1. Perform ! ReadableStreamFulfillReadRequest(_stream_, _filledView_, _done_). 1. Otherwise, 1. Assert: _pullIntoDescriptor_.[[readerType]] is `"byob"`. 1. Perform ! ReadableStreamFulfillReadIntoRequest(_stream_, _filledView_, _done_).

3.12.7. ReadableByteStreamControllerConvertPullIntoDescriptor ( pullIntoDescriptor )

1. Let _bytesFilled_ be _pullIntoDescriptor_.[[bytesFilled]]. 1. Let _elementSize_ be _pullIntoDescriptor_.[[elementSize]]. 1. Assert: _bytesFilled_ ≤ _pullIntoDescriptor_.[[byteLength]]. 1. Assert: _bytesFilled_ mod _elementSize_ is *0*. 1. Return ! Construct(_pullIntoDescriptor_.[[ctor]], « _pullIntoDescriptor_.[[buffer]], _pullIntoDescriptor_.[[byteOffset]], _bytesFilled_ ÷ _elementSize_ »).

3.12.8. ReadableByteStreamControllerEnqueue ( controller , chunk )

1. Let _stream_ be _controller_.[[controlledReadableStream]]. 1. Assert: _controller_.[[closeRequested]] is *false*. 1. Assert: _stream_.[[state]] is `"readable"`. 1. Let _buffer_ be _chunk_.[[ViewedArrayBuffer]]. 1. Let _byteOffset_ be _chunk_.[[ByteOffset]]. 1. Let _byteLength_ be _chunk_.[[ByteLength]]. 1. Let _transferredBuffer_ be ! Transfer (_buffer_, the current Realm Record). 1. If ! ReadableStreamHasDefaultReader(_stream_) is *true* 1. If ! ReadableStreamGetNumReadRequests(_stream_) is *0*, 1. Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(_controller_, _transferredBuffer_, _byteOffset_, _byteLength_). 1. Otherwise, 1. Assert: _controller_.[[queue]] is empty. 1. Let _transferredView_ be ! Construct( %Uint8Array% , « _transferredBuffer_, _byteOffset_, _byteLength_ »). 1. Perform ! ReadableStreamFulfillReadRequest(_stream_, _transferredView_, *false*). 1. Otherwise, if ! ReadableStreamHasBYOBReader(_stream_) is *true*, 1. Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(_controller_, _transferredBuffer_, _byteOffset_, _byteLength_). 1. Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(_controller_). 1. Otherwise, 1. Assert: ! IsReadableStreamLocked(_stream_) is *false*. 1. Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(_controller_, _transferredBuffer_, _byteOffset_, _byteLength_).

3.12.9. ReadableByteStreamControllerEnqueueChunkToQueue ( controller , buffer , byteOffset , byteLength )

1. Append Record {[[buffer]]: _buffer_, [[byteOffset]]: _byteOffset_, [[byteLength]]: _byteLength_} as the last element of _controller_.[[queue]]. 1. Add _byteLength_ to _controller_.[[queueTotalSize]].

3.12.10. ReadableByteStreamControllerError ( controller , e )

1. Let _stream_ be _controller_.[[controlledReadableStream]]. 1. Assert: _stream_.[[state]] is `"readable"`. 1. Perform ! ReadableByteStreamControllerClearPendingPullIntos(_controller_). 1. Perform ! ResetQueue(_controller_). 1. Perform ! ReadableStreamError(_stream_, _e_).

3.12.11. ReadableByteStreamControllerFillHeadPullIntoDescriptor ( controller , size , pullIntoDescriptor )

1. Assert: either _controller_.[[pendingPullIntos]] is empty, or the first element of _controller_.[[pendingPullIntos]] is _pullIntoDescriptor_. 1. Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(_controller_). 1. Set _pullIntoDescriptor_.[[bytesFilled]] to _pullIntoDescriptor_.[[bytesFilled]] + _size_.

3.12.12. ReadableByteStreamControllerFillPullIntoDescriptorFromQueue ( controller , pullIntoDescriptor )

1. Let _elementSize_ be _pullIntoDescriptor_.[[elementSize]]. 1. Let _currentAlignedBytes_ be _pullIntoDescriptor_.[[bytesFilled]] − (_pullIntoDescriptor_.[[bytesFilled]] mod _elementSize_). 1. Let _maxBytesToCopy_ be min(_controller_.[[queueTotalSize]], _pullIntoDescriptor_.[[byteLength]] − _pullIntoDescriptor_.[[bytesFilled]]). 1. Let _maxBytesFilled_ be _pullIntoDescriptor_.[[bytesFilled]] + _maxBytesToCopy_. 1. Let _maxAlignedBytes_ be _maxBytesFilled_ − (_maxBytesFilled_ mod _elementSize_). 1. Let _totalBytesToCopyRemaining_ be _maxBytesToCopy_. 1. Let _ready_ be *false*. 1. If _maxAlignedBytes_ > _currentAlignedBytes_, 1. Set _totalBytesToCopyRemaining_ to _maxAlignedBytes_ − _pullIntoDescriptor_.[[bytesFilled]]. 1. Set _ready_ to *true*. 1. Let _queue_ be _controller_.[[queue]]. 1. Repeat the following steps while _totalBytesToCopyRemaining_ > *0*, 1. Let _headOfQueue_ be the first element of _queue_. 1. Let _bytesToCopy_ be min(_totalBytesToCopyRemaining_, _headOfQueue_.[[byteLength]]). 1. Let _destStart_ be _pullIntoDescriptor_.[[byteOffset]] + _pullIntoDescriptor_.[[bytesFilled]]. 1. Perform ! CopyDataBlockBytes(_headOfQueue_.[[buffer]].[[ArrayBufferData]], _headOfQueue_.[[byteOffset]], _pullIntoDescriptor_.[[buffer]].[[ArrayBufferData]], _destStart_, _bytesToCopy_). 1. If _headOfQueue_.[[byteLength]] is _bytesToCopy_, 1. Remove the first element of _queue_, shifting all other elements downward (so that the second becomes the first, and so on). 1. Otherwise, 1. Set _headOfQueue_.[[byteOffset]] to _headOfQueue_.[[byteOffset]] + _bytesToCopy_. 1. Set _headOfQueue_.[[byteLength]] to _headOfQueue_.[[byteLength]] − _bytesToCopy_. 1. Set _controller_.[[queueTotalSize]] to _controller_.[[queueTotalSize]] − _bytesToCopy_. 1. Perform ! ReadableByteStreamControllerFillHeadPullIntoDescriptor(_controller_, _bytesToCopy_, _pullIntoDescriptor_). 1. Set _totalBytesToCopyRemaining_ to _totalBytesToCopyRemaining_ − _bytesToCopy_. 1. If _ready_ is *false*, 1. Assert: _controller_.[[queueTotalSize]] is *0*. 1. Assert: _pullIntoDescriptor_.[[bytesFilled]] > *0*. 1. Assert: _pullIntoDescriptor_.[[bytesFilled]] < _pullIntoDescriptor_.[[elementSize]]. 1. Return _ready_.

3.12.13. ReadableByteStreamControllerGetDesiredSize ( controller )

1. Let _stream_ be _controller_.[[controlledReadableStream]]. 1. Let _state_ be _stream_.[[state]]. 1. If _state_ is `"errored"`, return *null*. 1. If _state_ is `"closed"`, return *0*. 1. Return _controller_.[[strategyHWM]] − _controller_.[[queueTotalSize]].

3.12.14. ReadableByteStreamControllerHandleQueueDrain ( controller )

1. Assert: _controller_.[[controlledReadableStream]].[[state]] is `"readable"`. 1. If _controller_.[[queueTotalSize]] is *0* and _controller_.[[closeRequested]] is *true*, 1. Perform ! ReadableStreamClose(_controller_.[[controlledReadableStream]]). 1. Otherwise, 1. Perform ! ReadableByteStreamControllerCallPullIfNeeded(_controller_).

3.12.15. ReadableByteStreamControllerInvalidateBYOBRequest ( controller )

1. If _controller_.[[byobRequest]] is *undefined*, return. 1. Set _controller_.[[byobRequest]].[[associatedReadableByteStreamController]] to *undefined*. 1. Set _controller_.[[byobRequest]].[[view]] to *undefined*. 1. Set _controller_.[[byobRequest]] to *undefined*.

3.12.16. ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue ( controller )

1. Assert: _controller_.[[closeRequested]] is *false*. 1. Repeat the following steps while _controller_.[[pendingPullIntos]] is not empty, 1. If _controller_.[[queueTotalSize]] is *0*, return. 1. Let _pullIntoDescriptor_ be the first element of _controller_.[[pendingPullIntos]]. 1. If ! ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(_controller_, _pullIntoDescriptor_) is *true*, 1. Perform ! ReadableByteStreamControllerShiftPendingPullInto(_controller_). 1. Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(_controller_.[[controlledReadableStream]], _pullIntoDescriptor_).

3.12.17. ReadableByteStreamControllerPullInto ( controller , view )

1. Let _stream_ be _controller_.[[controlledReadableStream]]. 1. Let _elementSize_ be 1. 1. Let _ctor_ be %DataView%. 1. If _view_ has a [[TypedArrayName]] internal slot (i.e., it is not a ` DataView `), 1. Set _elementSize_ to the element size specified in the typed array constructors table for _view_.[[TypedArrayName]]. 1. Set _ctor_ to the constructor specified in the typed array constructors table for _view_.[[TypedArrayName]]. 1. Let _pullIntoDescriptor_ be Record {[[buffer]]: _view_.[[ViewedArrayBuffer]], [[byteOffset]]: _view_.[[ByteOffset]], [[byteLength]]: _view_.[[ByteLength]], [[bytesFilled]]: *0*, [[elementSize]]: _elementSize_, [[ctor]]: _ctor_, [[readerType]]: `"byob"`}. 1. If _controller_.[[pendingPullIntos]] is not empty, 1. Set _pullIntoDescriptor_.[[buffer]] to ! Transfer (_pullIntoDescriptor_.[[buffer]], the current Realm Record). 1. Append _pullIntoDescriptor_ as the last element of _controller_.[[pendingPullIntos]]. 1. Return ! ReadableStreamAddReadIntoRequest(_stream_). 1. If _stream_.[[state]] is `"closed"`, 1. Let _emptyView_ be ! Construct(_ctor_, « _pullIntoDescriptor_.[[buffer]], _pullIntoDescriptor_.[[byteOffset]], *0* »). 1. Return a promise resolved with ! CreateIterResultObject(_emptyView_, *true*). 1. If _controller_.[[queueTotalSize]] > *0*, 1. If ! ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(_controller_, _pullIntoDescriptor_) is *true*, 1. Let _filledView_ be ! ReadableByteStreamControllerConvertPullIntoDescriptor(_pullIntoDescriptor_). 1. Perform ! ReadableByteStreamControllerHandleQueueDrain(_controller_). 1. Return a promise resolved with ! CreateIterResultObject(_filledView_, *false*). 1. If _controller_.[[closeRequested]] is *true*, 1. Let _e_ be a *TypeError* exception. 1. Perform ! ReadableByteStreamControllerError(_controller_, _e_). 1. Return a promise rejected with _e_. 1. Set _pullIntoDescriptor_.[[buffer]] to ! Transfer (_pullIntoDescriptor_.[[buffer]], the current Realm Record). 1. Append _pullIntoDescriptor_ as the last element of _controller_.[[pendingPullIntos]]. 1. Let _promise_ be ! ReadableStreamAddReadIntoRequest(_stream_). 1. Perform ! ReadableByteStreamControllerCallPullIfNeeded(_controller_). 1. Return _promise_.

3.12.18. ReadableByteStreamControllerRespond ( controller , bytesWritten )

1. Let _bytesWritten_ be ? ToNumber(_bytesWritten_). 1. If ! IsFiniteNonNegativeNumber(_bytesWritten_) is *false*, 1. Throw a *RangeError* exception. 1. Assert: _controller_.[[pendingPullIntos]] is not empty. 1. Perform ? ReadableByteStreamControllerRespondInternal(_controller_, _bytesWritten_).

3.12.19. ReadableByteStreamControllerRespondInClosedState ( controller , firstDescriptor )

1. Set _firstDescriptor_.[[buffer]] to ! Transfer (_firstDescriptor_.[[buffer]], the current Realm Record). 1. Assert: _firstDescriptor_.[[bytesFilled]] is *0*. 1. Let _stream_ be _controller_.[[controlledReadableStream]]. 1. Repeat the following steps while ! ReadableStreamGetNumReadIntoRequests(_stream_) > *0*, 1. Let _pullIntoDescriptor_ be ! ReadableByteStreamControllerShiftPendingPullInto(_controller_). 1. Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(_stream_, _pullIntoDescriptor_).

3.12.20. ReadableByteStreamControllerRespondInReadableState ( controller , bytesWritten , pullIntoDescriptor )

1. If _pullIntoDescriptor_.[[bytesFilled]] + _bytesWritten_ > _pullIntoDescriptor_.[[byteLength]], throw a *RangeError* exception. 1. Perform ! ReadableByteStreamControllerFillHeadPullIntoDescriptor(_controller_, _bytesWritten_, _pullIntoDescriptor_). 1. If _pullIntoDescriptor_.[[bytesFilled]] < _pullIntoDescriptor_.[[elementSize]], return. 1. Perform ! ReadableByteStreamControllerShiftPendingPullInto(_controller_). 1. Let _remainderSize_ be _pullIntoDescriptor_.[[bytesFilled]] mod _pullIntoDescriptor_.[[elementSize]]. 1. If _remainderSize_ > *0*, 1. Let _end_ be _pullIntoDescriptor_.[[byteOffset]] + _pullIntoDescriptor_.[[bytesFilled]]. 1. Let _remainder_ be ? CloneArrayBuffer(_pullIntoDescriptor_.[[buffer]], _end_ − _remainderSize_, _remainderSize_, %ArrayBuffer%). 1. Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(_controller_, _remainder_, *0*, _remainder_.[[ByteLength]]). 1. Set _pullIntoDescriptor_.[[buffer]] to ! Transfer (_pullIntoDescriptor_.[[buffer]], the current Realm Record). 1. Set _pullIntoDescriptor_.[[bytesFilled]] to _pullIntoDescriptor_.[[bytesFilled]] − _remainderSize_. 1. Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(_controller_.[[controlledReadableStream]], _pullIntoDescriptor_). 1. Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(_controller_).

3.12.21. ReadableByteStreamControllerRespondInternal ( controller , bytesWritten )

1. Let _firstDescriptor_ be the first element of _controller_.[[pendingPullIntos]]. 1. Let _stream_ be _controller_.[[controlledReadableStream]]. 1. If _stream_.[[state]] is `"closed"`, 1. If _bytesWritten_ is not *0*, throw a *TypeError* exception. 1. Perform ! ReadableByteStreamControllerRespondInClosedState(_controller_, _firstDescriptor_). 1. Otherwise, 1. Assert: _stream_.[[state]] is `"readable"`. 1. Perform ? ReadableByteStreamControllerRespondInReadableState(_controller_, _bytesWritten_, _firstDescriptor_).

3.12.22. ReadableByteStreamControllerRespondWithNewView ( controller , view )

1. Assert: _controller_.[[pendingPullIntos]] is not empty. 1. Let _firstDescriptor_ be the first element of _controller_.[[pendingPullIntos]]. 1. If _firstDescriptor_.[[byteOffset]] + _firstDescriptor_.[[bytesFilled]] is not _view_.[[ByteOffset]], throw a *RangeError* exception. 1. If _firstDescriptor_.[[byteLength]] is not _view_.[[ByteLength]], throw a *RangeError* exception. 1. Set _firstDescriptor_.[[buffer]] to _view_.[[ViewedArrayBuffer]]. 1. Perform ? ReadableByteStreamControllerRespondInternal(_controller_, _view_.[[ByteLength]]).

3.12.23. ReadableByteStreamControllerShiftPendingPullInto ( controller )

1. Let _descriptor_ be the first element of _controller_.[[pendingPullIntos]]. 1. Remove _descriptor_ from _controller_.[[pendingPullIntos]], shifting all other elements downward (so that the second becomes the first, and so on). 1. Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(_controller_). 1. Return _descriptor_.

3.12.24. ReadableByteStreamControllerShouldCallPull ( controller )

1. Let _stream_ be _controller_.[[controlledReadableStream]]. 1. If _stream_.[[state]] is not `"readable"`, return *false*. 1. If _controller_.[[closeRequested]] is *true*, return *false*. 1. If _controller_.[[started]] is *false*, return *false*. 1. If ! ReadableStreamHasDefaultReader(_stream_) is *true* and ! ReadableStreamGetNumReadRequests(_stream_) > *0*, return *true*. 1. If ! ReadableStreamHasBYOBReader(_stream_) is *true* and ! ReadableStreamGetNumReadIntoRequests(_stream_) > *0*, return *true*. 1. If ! ReadableByteStreamControllerGetDesiredSize(_controller_) > *0*, return *true*. 1. Return *false*.

4. Writable Streams

4.1. Using Writable Streams

The usual way to write to a writable stream is to simply pipe a readable stream to it. This ensures that backpressure is respected, so that if the writable stream’s underlying sink is not able to accept data as fast as the readable stream can produce it, the readable stream is informed of this and has a chance to slow down its data production.
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 write() and 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));
  return writer.close();
}
writeArrayToStream([1, 2, 3, 4, 5], writableStream)
  .then(() => console.log("All done!"))
  .catch(e => console.error("Error with the stream: " + e));

In the previous example we only paid attention to the success or failure of the entire stream, by looking at the promise returned by its close() method. That promise (which can also be accessed using the closed getter) 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 stream’s 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.

The desiredSize and 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 desiredSize to determine how many bytes to generate at a given time, and using 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);
    window.crypto.getRandomValues(bytes);
    await writer.write(bytes);
  }
}
writeRandomBytesForever(myWritableStream).catch(e => console.error("Something broke", e));

4.2. Design Of The State Machine

In addition to the principles for streams in general, a number of additional considerations have informed the design of the WritableStream state machine.

Some of these design decisions improve predictability, ease-of-use, and safety for developers at the expense of making implementations more complex.

4.3. Class WritableStream

WritableStream

In only one current engine.

Firefox None Safari None Chrome 59+
Opera 47+ Edge 79+
Edge (Legacy) 16+ IE None
Firefox for Android None iOS Safari None Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 44+

4.2.1. 4.3.1. Class Definition

This section is non-normative.

If one were to write the WritableStream class in something close to the syntax of [ECMASCRIPT] , it would look like

class WritableStream {
  constructor(underlyingSink = {}, { size, highWaterMark = 1 } = {})
  get locked()
  abort(reason)
  getWriter()
}

4.2.2. 4.3.2. Internal Slots

Instances of WritableStream are created with the internal slots described in the following table:

Internal Slot Description ( non-normative )
[[backpressure]] The backpressure signal set by the controller
[[closeRequest]] The promise returned from the writer close() method
[[inFlightWriteRequest]] A slot set to the promise for the current in-flight write operation while the underlying sink ’s write method 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 method is executing and has not yet fulfilled, used to prevent the abort() method from interrupting close
[[pendingAbortRequest]] A Record containing the promise returned from abort() and the reason passed to abort()
[[state]] A string containing the stream’s current state, used internally; one of "writable" , "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 the stream while in the "errored" state
[[writableStreamController]] A WritableStreamDefaultController created with the ability to control the state and queue of this stream; also used for the IsWritableStream brand check
[[writer]] A WritableStreamDefaultWriter instance, if the stream is locked to a writer , or undefined if it is not
[[writeRequests]] A List of promises representing the stream’s internal queue of write requests not yet processed by the underlying sink .

The [[inFlightCloseRequest]] slot and [[closeRequest]] slot are mutually exclusive. Similarly, no element will be removed from [[writeRequests]] while [[inFlightWriteRequest]] is not undefined . Implementations can optimize storage for these slots based on these invariants.

4.2.3. 4.3.3. new WritableStream( underlyingSink = {}, { size , highWaterMark = 1 } = {})

WritableStream/WritableStream

In only one current engine.

Firefox None Safari None Chrome 59+
Opera 47+ Edge 79+
Edge (Legacy) 16+ IE None
Firefox for Android None iOS Safari None Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 44+
The underlyingSink object passed to the constructor can implement any of the following methods to govern how the constructed stream instance behaves:

The controller object passed to start , write and close is an instance of WritableStreamDefaultController , and has the ability to error the stream.

The constructor also accepts a second argument containing the queuing strategy object with two properties: a non-negative number highWaterMark , and a function size(chunk) . The supplied strategy could be an instance of the built-in CountQueuingStrategy or ByteLengthQueuingStrategy classes, or it could be custom. If no strategy is supplied, the default behavior will be the same as a CountQueuingStrategy with a high water mark of 1.

1. Set *this*.[[state]] to `"writable"`. 1. Set *this*.[[storedError]], *this*.[[writer]], *this*.[[writableStreamController]], *this*.[[inFlightWriteRequest]], *this*.[[closeRequest]], *this*.[[inFlightCloseRequest]] and *this*.[[pendingAbortRequest]] to *undefined*. 1. Set *this*.[[writeRequests]] to a new empty List. 1. Set *this*.[[backpressure]] to *false*. 1. Let _type_ be ? GetV(_underlyingSink_, `"type"`). 1. If _type_ is not *undefined*, throw a *RangeError* exception.

This is to allow us to add new potential types in the future, without backward-compatibility concerns.

1. Set *this*.[[writableStreamController]] to ? Construct(` WritableStreamDefaultController `, « *this*, _underlyingSink_, _size_, _highWaterMark_ »). 1. Perform ? *this*.[[writableStreamController]].[[StartSteps]]().

4.2.4. 4.3.4. Properties of the WritableStream Prototype

4.2.4.1. 4.3.4.1. get locked

WritableStream/locked

In only one current engine.

Firefox None Safari None Chrome 59+
Opera 47+ Edge 79+
Edge (Legacy) 16+ IE None
Firefox for Android None iOS Safari None Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 44+
The locked getter returns whether or not the writable stream is locked to a writer .
1. If ! IsWritableStream(*this*) is *false*, throw a *TypeError* exception. 1. Return ! IsWritableStreamLocked(*this*).
4.2.4.2. 4.3.4.2. abort( reason )

WritableStream/abort

In only one current engine.

Firefox None Safari None Chrome 59+
Opera 47+ Edge 79+
Edge (Legacy) 16+ IE None
Firefox for Android None iOS Safari None Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 44+
The abort method aborts the stream, signaling that the producer can no longer successfully write to the stream and it should be immediately moved to an errored state, with any queued-up writes discarded. This will also execute any abort mechanism of the underlying sink .
1. If ! IsWritableStream(*this*) is *false*, return a promise rejected with a *TypeError* exception. 1. If ! IsWritableStreamLocked(*this*) is *true*, return a promise rejected with a *TypeError* exception. 1. Return ! WritableStreamAbort(*this*, _reason_).
4.2.4.3. 4.3.4.3. getWriter()

WritableStream/getWriter

In only one current engine.

Firefox None Safari None Chrome 59+
Opera 47+ Edge 79+
Edge (Legacy) 16+ IE None
Firefox for Android None iOS Safari None Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 44+
The getWriter method creates a writer (an instance of WritableStreamDefaultWriter ) and locks the stream to the new writer. While the stream is locked, no other writer can be acquired until this one is released .

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

1. If ! IsWritableStream(*this*) is *false*, throw a *TypeError* exception. 1. Return ? AcquireWritableStreamDefaultWriter(*this*).

4.3. 4.4. General Writable Stream Abstract Operations

The following abstract operations, unlike most in this specification, are meant to be generally useful by other specifications, instead of just being part of the implementation of this spec’s classes.

4.3.1. 4.4.1. AcquireWritableStreamDefaultWriter ( stream )

1. Return ? Construct(` WritableStreamDefaultWriter `, « _stream_ »).

4.3.2. 4.4.2. IsWritableStream ( x )

1. If Type(_x_) is not Object, return *false*. 1. If _x_ does not have a [[writableStreamController]] internal slot, return *false*. 1. Return *true*.

4.3.3. 4.4.3. IsWritableStreamLocked ( stream )

This abstract operation is meant to be called from other specifications that may wish to query whether or not a writable stream is locked to a writer .

1. Assert: ! IsWritableStream(_stream_) is *true*. 1. If _stream_.[[writer]] is *undefined*, return *false*. 1. Return *true*.

4.3.4. 4.4.4. WritableStreamAbort ( stream , reason )

1. Let _state_ be _stream_.[[state]]. 1. If _state_ is `"closed"`, return a promise resolved with *undefined*. 1. If _state_ is `"errored"`, return a promise rejected with _stream_.[[storedError]]. 1. Assert: _state_ is `"writable"`. 1. Let _error_ be a new *TypeError* indicating that the stream has been requested to abort. 1. If _stream_.[[pendingAbortRequest]] is not *undefined*, return a promise rejected with _error_. 1. Let _controller_ be _stream_.[[writableStreamController]]. 1. Assert: _controller_ is not *undefined*. 1. If ! WritableStreamHasOperationMarkedInFlight(_stream_) is *false* and _controller_.[[started]] is *true*, 1. Perform ! WritableStreamFinishAbort(_stream_). 1. Return ! _controller_.[[AbortSteps]](_reason_). 1. Let _writer_ be _stream_.[[writer]]. 1. If _writer_ is not *undefined*, 1. Perform ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(_writer_, _error_). 1. Let _promise_ be a new promise . 1. Set _stream_.[[pendingAbortRequest]] to Record {[[promise]]: _promise_, [[reason]]: _reason_}. 1. Return _promise_.

4.3.5. 4.4.5. WritableStreamError ( stream , error )

1. Set _stream_.[[state]] to `"errored"`. 1. Set _stream_.[[storedError]] to _error_. 1. Perform ! _stream_.[[writableStreamController]].[[ErrorSteps]](). 1. If _stream_.[[pendingAbortRequest]] is *undefined*, 1. Let _writer_ be _stream_.[[writer]]. 1. If _writer_ is not *undefined*, 1. Perform ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(_writer_, _error_). 1. If ! WritableStreamHasOperationMarkedInFlight(_stream_) is *false*, perform ! WritableStreamRejectPromisesInReactionToError(_stream_).

4.3.6. 4.4.6. WritableStreamFinishAbort ( stream )

1. Let _error_ be a new *TypeError* indicating that the stream has been aborted. 1. Perform ! WritableStreamError(_stream_, _error_).

4.4. 4.5. Writable Stream Abstract Operations Used by Controllers

To allow future flexibility to add different writable stream behaviors (similar to the distinction between simple readable streams and readable byte streams ), much of the internal state of a writable stream is encapsulated by the WritableStreamDefaultController class. At this point in time the division of work between the stream and its controller may seems somewhat arbitrary, but centralizing much of the logic in the controller is a useful structure for the future.

The abstract operations in this section are interfaces that are used by the controller implementation to affect its associated WritableStream object, translating the controller’s internal state changes into developer-facing results visible through the WritableStream 's public API.

4.4.1. 4.5.1. WritableStreamAddWriteRequest ( stream )

1. Assert: ! IsWritableStreamLocked(_stream_) is *true*. 1. Assert: _stream_.[[state]] is `"writable"`. 1. Let _promise_ be a new promise . 1. Append _promise_ as the last element of _stream_.[[writeRequests]]. 1. Return _promise_.

4.4.2. 4.5.2. WritableStreamFinishInFlightWrite ( stream )

1. Assert: _stream_.[[inFlightWriteRequest]] is not *undefined*. 1. Resolve _stream_.[[inFlightWriteRequest]] with *undefined*. 1. Set _stream_.[[inFlightWriteRequest]] to *undefined*. 1. Let _state_ be _stream_.[[state]]. 1. If _state_ is `"errored"`, 1. Perform ! WritableStreamFinishInFlightWriteInErroredState(_stream_). 1. Return. 1. Assert: _state_ is `"writable"`. 1. Perform ! WritableStreamHandleAbortRequestIfPending(_stream_).

4.4.3. 4.5.3. WritableStreamFinishInFlightWriteInErroredState ( stream )

1. Perform ! WritableStreamRejectAbortRequestIfPending(_stream_). 1. Perform ! WritableStreamRejectPromisesInReactionToError(_stream_).

4.4.4. 4.5.4. WritableStreamFinishInFlightWriteWithError ( stream , error )

1. Assert: _stream_.[[inFlightWriteRequest]] is not *undefined*. 1. Reject _stream_.[[inFlightWriteRequest]] with _error_. 1. Set _stream_.[[inFlightWriteRequest]] to *undefined*. 1. Let _state_ be _stream_.[[state]]. 1. If _state_ is `"errored"`, 1. Perform ! WritableStreamFinishInFlightWriteInErroredState(_stream_). 1. Return. 1. Assert: _state_ is `"writable"`. 1. Perform ! WritableStreamError(_stream_, _error_). 1. Perform ! WritableStreamRejectAbortRequestIfPending(_stream_).

4.4.5. 4.5.5. WritableStreamFinishInFlightClose ( stream )

1. Assert: _stream_.[[inFlightCloseRequest]] is not *undefined*. 1. Resolve _stream_.[[inFlightCloseRequest]] with *undefined*. 1. Set _stream_.[[inFlightCloseRequest]] to *undefined*. 1. Let _state_ be _stream_.[[state]]. 1. If _state_ is `"errored"`, 1. Perform ! WritableStreamFinishInFlightCloseInErroredState(_stream_). 1. Return. 1. Assert: _state_ is `"writable"`. 1. Set _stream_.[[state]] to `"closed"`. 1. Let _writer_ be _stream_.[[writer]]. 1. If _writer_ is not *undefined*, resolve _writer_.[[closedPromise]] with *undefined*. 1. If _stream_.[[pendingAbortRequest]] is not *undefined*, 1. Resolve _stream_.[[pendingAbortRequest]].[[promise]] with *undefined*. 1. Set _stream_.[[pendingAbortRequest]] to *undefined*.

4.4.6. 4.5.6. WritableStreamFinishInFlightCloseInErroredState ( stream )

1. Perform ! WritableStreamRejectAbortRequestIfPending(_stream_). 1. Perform ! WritableStreamRejectClosedPromiseInReactionToError(_stream_).

4.4.7. 4.5.7. WritableStreamFinishInFlightCloseWithError ( stream , error )

1. Assert: _stream_.[[inFlightCloseRequest]] is not *undefined*. 1. Reject _stream_.[[inFlightCloseRequest]] with _error_. 1. Set _stream_.[[inFlightCloseRequest]] to *undefined*. 1. Let _state_ be _stream_.[[state]]. 1. If _state_ is `"errored"`, 1. Perform ! WritableStreamFinishInFlightCloseInErroredState(_stream_). 1. Return. 1. Assert: _state_ is `"writable"`. 1. Perform ! WritableStreamError(_stream_, _error_). 1. Perform ! WritableStreamRejectAbortRequestIfPending(_stream_).

4.4.8. 4.5.8. WritableStreamCloseQueuedOrInFlight ( stream )

1. If _stream_.[[closeRequest]] is *undefined* and _stream_.[[inFlightCloseRequest]] is *undefined*, return *false*. 1. Return *true*.

4.4.9. 4.5.9. WritableStreamHandleAbortRequestIfPending ( stream )

1. If _stream_.[[pendingAbortRequest]] is *undefined*, return. 1. Perform ! WritableStreamFinishAbort(_stream_). 1. Let _abortRequest_ be _stream_.[[pendingAbortRequest]]. 1. Set _stream_.[[pendingAbortRequest]] to *undefined*. 1. Let _promise_ be ! _stream_.[[writableStreamController]].[[AbortSteps]](_abortRequest_.[[reason]]). 1. Upon fulfillment of _promise_ with value _result_, resolve _abortRequest_.[[promise]] with _result_. 1. Upon rejection of _promise_ with reason _reason_, reject _abortRequest_.[[promise]] with _reason_.

4.4.10. 4.5.10. WritableStreamHasOperationMarkedInFlight ( stream )

1. If _stream_.[[inFlightWriteRequest]] is *undefined* and _controller_.[[inFlightCloseRequest]] is *undefined*, return *false*. 1. Return *true*.

4.4.11. 4.5.11. WritableStreamMarkCloseRequestInFlight ( stream )

1. Assert: _stream_.[[inFlightCloseRequest]] is *undefined*. 1. Assert: _stream_.[[closeRequest]] is not *undefined*. 1. Set _stream_.[[inFlightCloseRequest]] to _stream_.[[closeRequest]]. 1. Set _stream_.[[closeRequest]] to *undefined*.

4.4.12. 4.5.12. WritableStreamMarkFirstWriteRequestInFlight ( stream )

1. Assert: _stream_.[[inFlightWriteRequest]] is *undefined*. 1. Assert: _stream_.[[writeRequests]] is not empty. 1. Let _writeRequest_ be the first element of _stream_.[[writeRequests]]. 1. Remove _writeRequest_ from _stream_.[[writeRequests]], shifting all other elements downward (so that the second becomes the first, and so on). 1. Set _stream_.[[inFlightWriteRequest]] to _writeRequest_.

4.4.13. 4.5.13. WritableStreamRejectClosedPromiseInReactionToError ( stream )

1. Let _writer_ be _stream_.[[writer]]. 1. If _writer_ is not *undefined*, 1. Reject _writer_.[[closedPromise]] with _stream_.[[storedError]]. 1. Set _writer_.[[closedPromise]].[[PromiseIsHandled]] to *true*.

4.4.14. 4.5.14. WritableStreamRejectAbortRequestIfPending ( stream )

1. If _stream_.[[pendingAbortRequest]] is not *undefined*, 1. Reject _stream_.[[pendingAbortRequest]].[[promise]] with _stream_.[[storedError]]. 1. Set _stream_.[[pendingAbortRequest]] to *undefined*.

4.4.15. 4.5.15. WritableStreamRejectPromisesInReactionToError ( stream )

1. Let _storedError_ be _stream_.[[storedError]]. 1. Repeat for each _writeRequest_ that is an element of _stream_.[[writeRequests]], 1. Reject _writeRequest_ with _storedError_. 1. Set _stream_.[[writeRequests]] to an empty List. 1. If _stream_.[[closeRequest]] is not *undefined*, 1. Assert: _stream_.[[inFlightCloseRequest]] is *undefined*. 1. Reject _stream_.[[closeRequest]] with _storedError_. 1. Set _stream_.[[closeRequest]] to *undefined*. 1. Perform ! WritableStreamRejectClosedPromiseInReactionToError(_stream_).

4.4.16. 4.5.16. WritableStreamUpdateBackpressure ( stream , backpressure )

1. Assert: _stream_.[[state]] is `"writable"`. 1. Assert: WritableStreamCloseQueuedOrInFlight(_stream_) is *false*. 1. Let _writer_ be _stream_.[[writer]]. 1. If _writer_ is not *undefined* and _backpressure_ is not _stream_.[[backpressure]], 1. If _backpressure_ is *true*, set _writer_.[[readyPromise]] to a new promise . 1. Otherwise, 1. Assert: _backpressure_ is *false*. 1. Resolve _writer_.[[readyPromise]] with *undefined*. 1. Set _stream_.[[backpressure]] to _backpressure_.

4.5. 4.6. Class WritableStreamDefaultWriter

WritableStreamDefaultWriter

In only one current engine.

Firefox None Safari None Chrome 59+
Opera 46+ Edge 79+
Edge (Legacy) 16+ IE None
Firefox for Android None iOS Safari None Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 43+

The WritableStreamDefaultWriter class represents a writable stream writer designed to be vended by a WritableStream instance.

4.5.1. 4.6.1. Class Definition

This section is non-normative.

If one were to write the WritableStreamDefaultWriter class in something close to the syntax of [ECMASCRIPT] , it would look like

class WritableStreamDefaultWriter {
  constructor(stream)
  get closed()
  get desiredSize()
  get ready()
  abort(reason)
  close()
  releaseLock()
  write(chunk)
}

4.5.2. 4.6.2. 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 closed getter
[[ownerWritableStream]] A WritableStream instance that owns this writer
[[readyPromise]] A promise returned by the writer’s ready getter

4.5.3. 4.6.3. new WritableStreamDefaultWriter( stream )

WritableStreamDefaultWriter/WritableStreamDefaultWriter

In only one current engine.

Firefox None Safari None Chrome 59+
Opera 46+ Edge 79+
Edge (Legacy) 16+ IE None
Firefox for Android None iOS Safari None Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 43+
The WritableStreamDefaultWriter constructor is generally not meant to be used directly; instead, a stream’s getWriter() method should be used.
1. If ! IsWritableStream(_stream_) is *false*, throw a *TypeError* exception. 1. If ! IsWritableStreamLocked(_stream_) is *true*, throw a *TypeError* exception. 1. Set *this*.[[ownerWritableStream]] to _stream_. 1. Set _stream_.[[writer]] to *this*. 1. Let _state_ be _stream_.[[state]]. 1. If _state_ is `"writable"`, 1. If _stream_.[[pendingAbortRequest]] is not *undefined*, 1. Let _error_ be a new *TypeError* indicating that the stream has been requested to abort. 1. Set *this*.[[readyPromise]] to a promise rejected with _error_. 1. Set *this*.[[readyPromise]].[[PromiseIsHandled]] to *true*. 1. Otherwise, if ! WritableStreamCloseQueuedOrInFlight(_stream_) is *false* and _stream_.[[backpressure]] is *true*, set *this*.[[readyPromise]] to a new promise . 1. Otherwise, set *this*.[[readyPromise]] to a promise resolved with *undefined*. 1. Set *this*.[[closedPromise]] to a new promise . 1. Otherwise, if _state_ is `"closed"`, 1. Set *this*.[[readyPromise]] to a promise resolved with *undefined*. 1. Set *this*.[[closedPromise]] to a promise resolved with *undefined*. 1. Otherwise, 1. Assert: _state_ is `"errored"`. 1. Let _storedError_ be _stream_.[[storedError]]. 1. Set *this*.[[readyPromise]] to a promise rejected with _storedError_. 1. Set *this*.[[readyPromise]].[[PromiseIsHandled]] to *true*. 1. Set *this*.[[closedPromise]] to a promise rejected with _storedError_. 1. Set *this*.[[closedPromise]].[[PromiseIsHandled]] to *true*.

4.5.4. 4.6.4. Properties of the WritableStreamDefaultWriter Prototype

4.5.4.1. 4.6.4.1. get closed

WritableStreamDefaultWriter/closed

In only one current engine.

Firefox None Safari None Chrome 59+
Opera 46+ Edge 79+
Edge (Legacy) 16+ IE None
Firefox for Android None iOS Safari None Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 43+
The closed getter returns a promise that will be fulfilled when the stream becomes closed, or rejected if the stream ever errors or the writer’s lock is released before the stream finishes closing.
1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, return a promise rejected with a *TypeError* exception. 1. Return *this*.[[closedPromise]].
4.5.4.2. 4.6.4.2. get desiredSize
The desiredSize getter returns the desired size to fill the stream’s internal queue . It can be negative, if the queue is over-full. A producer should 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. The getter will throw an exception if invoked when the writer’s lock is released .

1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, throw a *TypeError* exception. 1. If *this*.[[ownerWritableStream]] is *undefined*, throw a *TypeError* exception. 1. Return ! WritableStreamDefaultWriterGetDesiredSize(*this*).
4.5.4.3. 4.6.4.3. get ready

WritableStreamDefaultWriter/ready

In only one current engine.

Firefox None Safari None Chrome 59+
Opera 46+ Edge 79+
Edge (Legacy) 16+ IE None
Firefox for Android None iOS Safari None Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 43+
The ready getter returns a promise that will be fulfilled when the desired size to fill the stream’s internal queue transitions from nonpositive to positive, signaling that it is no longer applying backpressure . Once the desired size to fill the stream’s internal queue dips back to zero or below, the getter will return a new promise that stays pending until the next transition.

If the stream becomes errored or aborted, or the writer’s lock is released , the returned promise will become rejected.

1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, return a promise rejected with a *TypeError* exception. 1. Return *this*.[[readyPromise]].
4.5.4.4. 4.6.4.4. abort( reason )

WritableStreamDefaultWriter/abort

In only one current engine.

Firefox None Safari None Chrome 59+
Opera 46+ Edge 79+
Edge (Legacy) 16+ IE None
Firefox for Android None iOS Safari None Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 43+
If the writer is active , the abort method behaves the same as that for the associated stream. (Otherwise, it returns a rejected promise.)
1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, return a promise rejected with a *TypeError* exception. 1. If *this*.[[ownerWritableStream]] is *undefined*, return a promise rejected with a *TypeError* exception. 1. Return ! WritableStreamDefaultWriterAbort(*this*, _reason_).
4.5.4.5. 4.6.4.5. close()

WritableStreamDefaultWriter/close

In only one current engine.

Firefox None Safari None Chrome 59+
Opera 46+ Edge 79+
Edge (Legacy) 16+ IE None
Firefox for Android None iOS Safari None Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 43+
The close method will close the associated writable 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 is fulfilled with undefined if all remaining chunks are successfully written and the stream successfully closes, or rejects if an error is encountered during this process. (This is the same promise as is returned by closed .)

1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, return a promise rejected with a *TypeError* exception. 1. Let _stream_ be *this*.[[ownerWritableStream]]. 1. If _stream_ is *undefined*, return a promise rejected with a *TypeError* exception. 1. If ! WritableStreamCloseQueuedOrInFlight(_stream_) is *true*, return a promise rejected with a *TypeError* exception. 1. Return ! WritableStreamDefaultWriterClose(*this*).
4.5.4.6. 4.6.4.6. releaseLock()
The releaseLock method releases the writer’s lock on the corresponding stream. After the lock is released, the writer is no longer active . If the associated stream is errored when the lock is released, the writer will appear errored in the same way from now on; otherwise, 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 write() have not yet settled). It’s not required 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.

1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, throw a *TypeError* exception. 1. Let _stream_ be *this*.[[ownerWritableStream]]. 1. If _stream_ is *undefined*, return. 1. Assert: _stream_.[[writer]] is not *undefined*. 1. Perform ! WritableStreamDefaultWriterRelease(*this*).
4.5.4.7. 4.6.4.7. write( chunk )

WritableStreamDefaultWriter/write

In only one current engine.

Firefox None Safari None Chrome 59+
Opera 46+ Edge 79+
Edge (Legacy) 16+ IE None
Firefox for Android None iOS Safari None Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 43+
The write method writes the given chunk to the writable stream, by waiting until any previous writes have finished successfully, and then sending the chunk to the underlying sink . 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 may indicate simply that the chunk has been accepted, and not necessarily that it is safely saved to its ultimate destination.

1. If ! IsWritableStreamDefaultWriter(*this*) is *false*, return a promise rejected with a *TypeError* exception. 1. If *this*.[[ownerWritableStream]] is *undefined*, return a promise rejected with a *TypeError* exception. 1. Return ! WritableStreamDefaultWriterWrite(*this*, _chunk_).

4.6. 4.7. Writable Stream Writer Abstract Operations

4.6.1. 4.7.1. IsWritableStreamDefaultWriter ( x )

1. If Type(_x_) is not Object, return *false*. 1. If _x_ does not have an [[ownerWritableStream]] internal slot, return *false*. 1. Return *true*.

4.6.2. 4.7.2. WritableStreamDefaultWriterAbort ( writer , reason )

1. Let _stream_ be _writer_.[[ownerWritableStream]]. 1. Assert: _stream_ is not *undefined*. 1. Return ! WritableStreamAbort(_stream_, _reason_).

4.6.3. 4.7.3. WritableStreamDefaultWriterClose ( writer )

1. Let _stream_ be _writer_.[[ownerWritableStream]]. 1. Assert: _stream_ is not *undefined*. 1. Let _state_ be _stream_.[[state]]. 1. If _state_ is `"closed"` or `"errored"`, return a promise rejected with a *TypeError* exception. 1. If _stream_.[[pendingAbortRequest]] is not *undefined*, return a promise rejected with a *TypeError* indicating that the stream has been requested to abort. 1. Assert: _state_ is `"writable"`. 1. Assert: ! WritableStreamCloseQueuedOrInFlight(_stream_) is *false*. 1. Let _promise_ be a new promise . 1. Set _stream_.[[closeRequest]] to _promise_. 1. If _stream_.[[backpressure]] is *true*, resolve _writer_.[[readyPromise]] with *undefined*. 1. Perform ! WritableStreamDefaultControllerClose(_stream_.[[writableStreamController]]). 1. Return _promise_.

4.6.4. 4.7.4. WritableStreamDefaultWriterCloseWithErrorPropagation ( writer )

This abstract operation helps implement the error propagation semantics of pipeTo() .

1. Let _stream_ be _writer_.[[ownerWritableStream]]. 1. Assert: _stream_ is not *undefined*. 1. Let _state_ be _stream_.[[state]]. 1. If ! WritableStreamCloseQueuedOrInFlight(_stream_) is *true* or _state_ is `"closed"`, return a promise resolved with *undefined*. 1. If _state_ is `"errored"`, return a promise rejected with _stream_.[[storedError]]. 1. Assert: _state_ is `"writable"`. 1. Return ! WritableStreamDefaultWriterClose(_writer_).

4.6.5. 4.7.5. WritableStreamDefaultWriterEnsureReadyPromiseRejected( writer , error )

1. If _writer_.[[readyPromise]].[[PromiseState]] is `"pending"`, reject _writer.[[readyPromise]] with _error_. 1. Otherwise, set _writer_.[[readyPromise]] to a promise rejected with _error_. 1. Set _writer_.[[readyPromise]].[[PromiseIsHandled]] to *true*.

4.6.6. 4.7.6. WritableStreamDefaultWriterGetDesiredSize ( writer )

1. Let _stream_ be _writer_.[[ownerWritableStream]]. 1. Let _state_ be _stream_.[[state]]. 1. If _state_ is `"errored"` or _stream_.[[pendingAbortRequest]] is not *undefined*, return *null*. 1. If _state_ is `"closed"`, return *0*. 1. Return ! WritableStreamDefaultControllerGetDesiredSize(_stream_.[[writableStreamController]]).

4.6.7. 4.7.7. WritableStreamDefaultWriterRelease ( writer )

1. Let _stream_ be _writer_.[[ownerWritableStream]]. 1. Assert: _stream_ is not *undefined*. 1. Assert: _stream_.[[writer]] is _writer_. 1. Let _releasedError_ be a new *TypeError*. 1. Let _state_ be _stream_.[[state]]. 1. Perform ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(_writer_, _releasedError_). 1. If _state_ is `"writable"` or ! WritableStreamHasOperationMarkedInFlight(_stream_) is *true*, reject _writer_.[[closedPromise]] with _releasedError_. 1. Otherwise, set _writer_.[[closedPromise]] to a promise rejected with _releasedError_. 1. Set _writer_.[[closedPromise]].[[PromiseIsHandled]] to *true*. 1. Set _stream_.[[writer]] to *undefined*. 1. Set _writer_.[[ownerWritableStream]] to *undefined*.

4.6.8. 4.7.8. WritableStreamDefaultWriterWrite ( writer , chunk )

1. Let _stream_ be _writer_.[[ownerWritableStream]]. 1. Assert: _stream_ is not *undefined*. 1. Let _controller_ be _stream_.[[writableStreamController]]. 1. Let _chunkSize_ be ! WritableStreamDefaultControllerGetChunkSize(_controller_, _chunk_). 1. If _stream_ is not equal to _writer_.[[ownerWritableStream]], return a promise rejected with a *TypeError* exception. 1. Let _state_ be _stream_.[[state]]. 1. If _state_ is `"errored"`, return a promise rejected with _stream_.[[storedError]]. 1. If ! WritableStreamCloseQueuedOrInFlight(_stream_) is *true* or _state_ is `"closed"`, return a promise rejected with a *TypeError* exception indicating that the stream is closing or closed. 1. Assert: _state_ is `"writable"`. 1. If _stream_.[[pendingAbortRequest]] is not *undefined*, return a promise rejected with a *TypeError* indicating that the stream is in the process of being aborted. 1. Let _promise_ be ! WritableStreamAddWriteRequest(_stream_). 1. Perform ! WritableStreamDefaultControllerWrite(_controller_, _chunk_, _chunkSize_). 1. Return _promise_.

4.7. 4.8. Class WritableStreamDefaultController

WritableStreamDefaultController

In only one current engine.

Firefox None Safari None Chrome 58+
Opera 45+ Edge 79+
Edge (Legacy) 18 IE None
Firefox for Android None iOS Safari None Chrome for Android 58+ Android WebView 58+ Samsung Internet 7.0+ Opera Mobile 43+

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.

4.7.1. 4.8.1. Class Definition

This section is non-normative.

If one were to write the WritableStreamDefaultController class in something close to the syntax of [ECMASCRIPT] , it would look like

class WritableStreamDefaultController {
  constructor(stream, underlyingSink, size, highWaterMark)
  error(e)
}

4.7.2. 4.8.2. Internal Slots

Instances of WritableStreamDefaultController are created with the internal slots described in the following table:

Internal Slot Description ( non-normative )
[[controlledWritableStream]] The WritableStream instance controlled
[[queue]] A List representing the stream’s internal queue of chunks
[[queueTotalSize]] The total size of all the chunks stored in [[queue]] (see § 6.3 Queue-with-Sizes Operations )
[[started]] A boolean flag indicating whether the underlying sink 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 sink
[[strategySize]] A function supplied to the constructor as part of the stream’s queuing strategy , designed to calculate the size of enqueued chunks ; can be undefined for the default behavior
[[underlyingSink]] An object representation of the stream’s underlying sink ; also used for the IsWritableStreamDefaultController brand check

4.7.3. 4.8.3. new WritableStreamDefaultController( stream , underlyingSink , size , highWaterMark )

The WritableStreamDefaultController constructor cannot be used directly; it only works on a WritableStream that is in the middle of being constructed.
1. If ! IsWritableStream(_stream_) is *false*, throw a *TypeError* exception. 1. If _stream_.[[writableStreamController]] is not *undefined*, throw a *TypeError* exception. 1. Set *this*.[[controlledWritableStream]] to _stream_. 1. Set *this*.[[underlyingSink]] to _underlyingSink_. 1. Perform ! ResetQueue(*this*). 1. Set *this*.[[started]] to *false*. 1. Let _normalizedStrategy_ be ? ValidateAndNormalizeQueuingStrategy(_size_, _highWaterMark_). 1. Set *this*.[[strategySize]] to _normalizedStrategy_.[[size]] and *this*.[[strategyHWM]] to _normalizedStrategy_.[[highWaterMark]]. 1. Let _backpressure_ be ! WritableStreamDefaultControllerGetBackpressure(*this*). 1. Perform ! WritableStreamUpdateBackpressure(_stream_, _backpressure_).

4.7.4. 4.8.4. Properties of the WritableStreamDefaultController Prototype

4.7.4.1. 4.8.4.1. error( e )

WritableStreamDefaultController/error

In no current engines.

Firefox None Safari None Chrome ?
Opera ? Edge ?
Edge (Legacy) 16+ IE None
Firefox for Android None iOS Safari None Chrome for Android ? Android WebView ? Samsung Internet ? Opera Mobile ?
The error method will error the writable stream, making all future interactions with it fail with the given error e .

This method is rarely used, since usually it suffices to return a rejected promise from one of the underlying sink ’s methods. However, it can be useful for suddenly shutting down a stream in response to an event outside the normal lifecycle of interactions with the underlying sink .

1. If ! IsWritableStreamDefaultController(*this*) is *false*, throw a *TypeError* exception. 1. Let _state_ be *this*.[[controlledWritableStream]].[[state]]. 1. If _state_ is `"closed"` or `"errored"`, throw a *TypeError* exception. 1. Perform ! WritableStreamDefaultControllerError(*this*, _e_).

4.7.5. 4.8.5. Writable Stream Default Controller Internal Methods

The following are additional internal methods implemented by each WritableStreamDefaultController instance. The writable stream implementation will call into these.

The reason these are in method form, instead of as abstract operations, is to make it clear that the writable stream implementation is decoupled from the controller implementation, and could in the future be expanded with other controllers, as long as those controllers implemented such internal methods. A similar scenario is seen for readable streams, where there actually are multiple controller types and as such the counterpart internal methods are used polymorphically.

4.7.5.1. 4.8.5.1. [[AbortSteps]]()
1. Let _sinkAbortPromise_ be ! PromiseInvokeOrNoop(*this*.[[underlyingSink]], `"abort"`, « _reason_ »). 1. Return the result of transforming _sinkAbortPromise_ with a fulfillment handler that returns *undefined*.
4.7.5.2. 4.8.5.2. [[ErrorSteps]]()
1. Perform ! ResetQueue(*this*).
4.7.5.3. 4.8.5.3. [[StartSteps]]()
1. Let _startResult_ be ? InvokeOrNoop(*this*.[[underlyingSink]], `"start"`, « *this* »). 1. Let _stream_ be *this*.[[controlledWritableStream]]. 1. Let _startPromise_ be a promise resolved with _startResult_: 1. Upon fulfillment of _startPromise_, 1. Set *this*.[[started]] to *true*. 1. If _stream_.[[state]] is `"errored"`, perform ! WritableStreamRejectAbortRequestIfPending(_stream_). 1. Otherwise, perform ! WritableStreamHandleAbortRequestIfPending(_stream_). 1. Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(*this*). 1. Upon rejection of _startPromise_ with reason _r_, 1. Assert: _stream_.[[state]] is `"writable"` or `"errored"`. 1. Perform ! WritableStreamDefaultControllerErrorIfNeeded(*this*, _r_). 1. Perform ! WritableStreamRejectAbortRequestIfPending(_stream_).

4.8. 4.9. Writable Stream Default Controller Abstract Operations

4.8.1. 4.9.1. IsWritableStreamDefaultController ( x )

1. If Type(_x_) is not Object, return *false*. 1. If _x_ does not have an [[underlyingSink]] internal slot, return *false*. 1. Return *true*.

4.8.2. 4.9.2. WritableStreamDefaultControllerClose ( controller )

1. Perform ! EnqueueValueWithSize(_controller_, `"close"`, *0*). 1. Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(_controller_).

4.8.3. 4.9.3. WritableStreamDefaultControllerGetChunkSize ( controller , chunk )

1. Let _strategySize_ be _controller_.[[strategySize]]. 1. If _strategySize_ is *undefined*, return 1. 1. Let _returnValue_ be Call(_strategySize_, *undefined*, « _chunk_ »). 1. If _returnValue_ is an abrupt completion, 1. Perform ! WritableStreamDefaultControllerErrorIfNeeded(_controller_, _returnValue_.[[Value]]). 1. Return 1. 1. Return _returnValue_.[[Value]].

4.8.4. 4.9.4. WritableStreamDefaultControllerGetDesiredSize ( controller )

1. Return _controller_.[[strategyHWM]] − _controller_.[[queueTotalSize]].

4.8.5. 4.9.5. WritableStreamDefaultControllerWrite ( controller , chunk , chunkSize )

1. Let _writeRecord_ be Record {[[chunk]]: _chunk_}. 1. Let _enqueueResult_ be ! EnqueueValueWithSize(_controller_, _writeRecord_, _chunkSize_). 1. If _enqueueResult_ is an abrupt completion, 1. Perform ! WritableStreamDefaultControllerErrorIfNeeded(_controller_, _enqueueResult_.[[Value]]). 1. Return. 1. Let _stream_ be _controller_.[[controlledWritableStream]]. 1. If ! WritableStreamCloseQueuedOrInFlight(_stream_) is *false*, 1. Let _backpressure_ be ! WritableStreamDefaultControllerGetBackpressure(_controller_). 1. Perform ! WritableStreamUpdateBackpressure(_stream_, _backpressure_). 1. Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(_controller_).

4.8.6. 4.9.6. WritableStreamDefaultControllerAdvanceQueueIfNeeded ( controller )

1. Let _stream_ be _controller_.[[controlledWritableStream]]. 1. Let _state_ be _stream_.[[state]]. 1. If _state_ is `"closed"` or `"errored"`, return. 1. If _controller_.[[started]] is *false*, return. 1. If _stream_.[[inFlightWriteRequest]] is not *undefined*, return. 1. If _controller_.[[queue]] is empty, return. 1. Let _writeRecord_ be ! PeekQueueValue(_controller_). 1. If _writeRecord_ is `"close"`, perform ! WritableStreamDefaultControllerProcessClose(_controller_). 1. Otherwise, perform ! WritableStreamDefaultControllerProcessWrite(_controller_, _writeRecord_.[[chunk]]).

4.8.7. 4.9.7. WritableStreamDefaultControllerErrorIfNeeded ( controller , error )

1. If _controller_.[[controlledWritableStream]].[[state]] is `"writable"`, perform ! WritableStreamDefaultControllerError(_controller_, _error_).

4.8.8. 4.9.8. WritableStreamDefaultControllerProcessClose ( controller )

1. Let _stream_ be _controller_.[[controlledWritableStream]]. 1. Perform ! WritableStreamMarkCloseRequestInFlight(_stream_). 1. Perform ! DequeueValue(_controller_). 1. Assert: _controller_.[[queue]] is empty. 1. Let _sinkClosePromise_ be ! PromiseInvokeOrNoop(_controller_.[[underlyingSink]], `"close"`, « _controller_ »). 1. Upon fulfillment of _sinkClosePromise_, 1. Perform ! WritableStreamFinishInFlightClose(_stream_). 1. Upon rejection of _sinkClosePromise_ with reason _reason_, 1. Perform ! WritableStreamFinishInFlightCloseWithError(_stream_, _reason_).

4.8.9. 4.9.9. WritableStreamDefaultControllerProcessWrite ( controller , chunk )

1. Let _stream_ be _controller_.[[controllerWritableStream]]. 1. Perform ! WritableStreamMarkFirstWriteRequestInFlight(_stream_). 1. Let _sinkWritePromise_ be ! PromiseInvokeOrNoop(_controller_.[[underlyingSink]], `"write"`, « _chunk_, _controller_ »). 1. Upon fulfillment of _sinkWritePromise_, 1. Perform ! WritableStreamFinishInFlightWrite(_stream_). 1. Let _state_ be _stream_.[[state]]. 1. If _state_ is `"errored"`, return. 1. Assert: _state_ is `"writable"`. 1. Perform ! DequeueValue(_controller_). 1. If ! WritableStreamCloseQueuedOrInFlight(_stream_) is *false*, 1. Let _backpressure_ be ! WritableStreamDefaultControllerGetBackpressure(_controller_). 1. Perform ! WritableStreamUpdateBackpressure(_stream_, _backpressure_). 1. Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(_controller_). 1. Upon rejection of _sinkWritePromise_ with _reason_, 1. Let _wasErrored_ be *false*. 1. If _stream_.[[state]] is `"errored"`, set _wasErrored_ to *true*. 1. Perform ! WritableStreamFinishInFlightWriteWithError(_stream_, _reason_). 1. Assert: _stream_.[[state]] is `"errored"`. 1. If _wasErrored_ is *false*, set _controller_.[[queue]] to an empty List.

4.8.10. 4.9.10. WritableStreamDefaultControllerGetBackpressure ( controller )

1. Let _desiredSize_ be ! WritableStreamDefaultControllerGetDesiredSize(_controller_). 1. Return _desiredSize_ ≤ *0*.

4.8.11. 4.9.11. WritableStreamDefaultControllerError ( controller , error )

1. Let _stream_ be _controller_.[[controlledWritableStream]]. 1. Assert: _stream_.[[state]] is `"writable"`. 1. Perform ! WritableStreamError(_stream_, _error_).

5. Transform Streams

Transform streams have been developed in the testable implementation, but not yet re-encoded in spec language. We are waiting to validate their design before doing so. In the meantime, see reference-implementation/lib/transform-stream.js .

6. Other Stream APIs and Operations

6.1. Class ByteLengthQueuingStrategy

ByteLengthQueuingStrategy

Firefox 🔰 57+ Safari ? Chrome 59+
Opera 46+ Edge 79+
Edge (Legacy) 16+ IE None
Firefox for Android 🔰 57+ iOS Safari ? Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 43+

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 .

6.1.1. Class Definition

This section is non-normative.

If one were to write the ByteLengthQueuingStrategy class in something close to the syntax of [ECMASCRIPT] , it would look like

class ByteLengthQueuingStrategy {
  constructor({ highWaterMark })
  size(chunk)
}

Each ByteLengthQueuingStrategy instance will additionally have an own data property highWaterMark set by its constructor.

6.1.2. new ByteLengthQueuingStrategy({ highWaterMark })

ByteLengthQueuingStrategy/ByteLengthQueuingStrategy

Firefox 🔰 57+ Safari ? Chrome 59+
Opera 46+ Edge 79+
Edge (Legacy) 16+ IE None
Firefox for Android 🔰 57+ iOS Safari ? Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 43+
The constructor takes a non-negative number for the high-water mark, and stores it on the object as a property.
1. Perform ! CreateDataProperty(*this*, `"highWaterMark"`, _highWaterMark_).

6.1.3. Properties of the ByteLengthQueuingStrategy Prototype

6.1.3.1. size( chunk )

ByteLengthQueuingStrategy/size

Firefox 🔰 57+ Safari ? Chrome 59+
Opera 46+ Edge 79+
Edge (Legacy) 16+ IE None
Firefox for Android 🔰 57+ iOS Safari ? Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 43+
The size method returns the given chunk’s byteLength property. (If the chunk doesn’t have one, it will return undefined , causing the stream using this strategy to error.)

This method is intentionally generic; it does not require that its this value be a ByteLengthQueuingStrategy object.

1. Return ? GetV(_chunk_, `"byteLength"`).

6.2. Class CountQueuingStrategy

CountQueuingStrategy

Firefox 🔰 57+ Safari ? Chrome 59+
Opera 46+ Edge 79+
Edge (Legacy) 16+ IE None
Firefox for Android 🔰 57+ iOS Safari ? Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 43+

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 .

6.2.1. Class Definition

This section is non-normative.

If one were to write the CountQueuingStrategy class in something close to the syntax of [ECMASCRIPT] , it would look like

class CountQueuingStrategy {
  constructor({ highWaterMark })
  size()
}

Each CountQueuingStrategy instance will additionally have an own data property highWaterMark set by its constructor.

6.2.2. new CountQueuingStrategy({ highWaterMark })

CountQueuingStrategy/CountQueuingStrategy

Firefox 🔰 57+ Safari ? Chrome 59+
Opera 46+ Edge 79+
Edge (Legacy) 16+ IE None
Firefox for Android 🔰 57+ iOS Safari ? Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 43+
The constructor takes a non-negative number for the high-water mark, and stores it on the object as a property.
1. Perform ! CreateDataProperty(*this*, `"highWaterMark"`, _highWaterMark_).

6.2.3. Properties of the CountQueuingStrategy Prototype

6.2.3.1. size()

CountQueuingStrategy/size

Firefox 🔰 57+ Safari ? Chrome 59+
Opera 46+ Edge 79+
Edge (Legacy) 16+ IE None
Firefox for Android 🔰 57+ iOS Safari ? Chrome for Android 59+ Android WebView 59+ Samsung Internet 7.0+ Opera Mobile 43+
The size method returns one always, so that the total queue size is a count of the number of chunks in the queue.

This method is intentionally generic; it does not require that its this value be a CountQueuingStrategy object.

1. Return *1*.

6.3. Queue-with-Sizes Operations

The streams in this specification use a "queue-with-sizes" data structure to store queued up values, along with their determined sizes. Various specification objects contain a queue-with-sizes, represented by the object having two paired internal slots, always named [[queue]] and [[queueTotalSize]]. [[queue]] is a List of Records with [[value]] and [[size]] fields, and [[queueTotalSize]] is a JavaScript Number , i.e. a double-precision floating point number.

The following abstract operations are used when operating on objects that contain queues-with-sizes, in order to ensure that the two internal slots stay synchronized.

Due to the limited precision of floating-point arithmetic, the framework specified here, of keeping a running total in the [[queueTotalSize]] slot, is not equivalent to adding up the size of all chunks in [[queue]]. (However, this only makes a difference when there is a huge (~10 15 ) variance in size between chunks, or when trillions of chunks are enqueued.)

6.3.1. DequeueValue ( container )

1. Assert: _container_ has [[queue]] and [[queueTotalSize]] internal slots. 1. Assert: _container_.[[queue]] is not empty. 1. Let _pair_ be the first element of _container_.[[queue]]. 1. Remove _pair_ from _container_.[[queue]], shifting all other elements downward (so that the second becomes the first, and so on). 1. Set _container_.[[queueTotalSize]] to _container_.[[queueTotalSize]] − _pair_.[[size]]. 1. If _container_.[[queueTotalSize]] < *0*, set _container_.[[queueTotalSize]] to *0*. (This can occur due to rounding errors.) 1. Return _pair_.[[value]].

6.3.2. EnqueueValueWithSize ( container , value , size )

1. Assert: _container_ has [[queue]] and [[queueTotalSize]] internal slots. 1. Let _size_ be ? ToNumber(_size_). 1. If ! IsFiniteNonNegativeNumber(_size_) is *false*, throw a *RangeError* exception. 1. Append Record {[[value]]: _value_, [[size]]: _size_} as the last element of _container_.[[queue]]. 1. Set _container_.[[queueTotalSize]] to _container_.[[queueTotalSize]] + _size_.

6.3.3. PeekQueueValue ( container )

1. Assert: _container_ has [[queue]] and [[queueTotalSize]] internal slots. 1. Assert: _container_.[[queue]] is not empty. 1. Let _pair_ be the first element of _container_.[[queue]]. 1. Return _pair_.[[value]].

6.3.4. ResetQueue ( container )

1. Assert: _container_ has [[queue]] and [[queueTotalSize]] internal slots. 1. Set _container_.[[queue]] to a new empty List. 1. Set _container_.[[queueTotalSize]] to *0*.

6.4. Miscellaneous Operations

A few abstract operations are used in this specification for utility purposes. We define them here.

6.4.1. InvokeOrNoop ( O , P , args )

InvokeOrNoop is a slight modification of the [ECMASCRIPT] Invoke abstract operation to return undefined when the method is not present.
1. Assert: _O_ is not *undefined*. 1. Assert: ! IsPropertyKey(_P_) is *true*. 1. Assert: _args_ is a List. 1. Let _method_ be ? GetV(_O_, _P_). 1. If _method_ is *undefined*, return *undefined*. 1. Return ? Call(_method_, _O_, _args_).

6.4.2. IsFiniteNonNegativeNumber ( v )

1. If _v_ is *NaN*, return *false*. 1. If _v_ is *+∞*, return *false*. 1. If _v_ < *0*, return *false*. 1. Return *true*.

6.4.3. PromiseInvokeOrNoop ( O , P , args )

PromiseInvokeOrNoop is a specialized version of promise-calling that both works on methods and returns a promise for undefined when the method is not present.
1. Assert: _O_ is not *undefined*. 1. Assert: ! IsPropertyKey(_P_) is *true*. 1. Assert: _args_ is a List. 1. Let _returnValue_ be InvokeOrNoop(_O_, _P_, _args_). 1. If _returnValue_ is an abrupt completion, return a promise rejected with _returnValue_.[[Value]]. 1. Otherwise, return a promise resolved with _returnValue_.[[Value]].

6.4.4. ValidateAndNormalizeHighWaterMark ( highWaterMark )

1. Set _highWaterMark_ to ? ToNumber(_highWaterMark_). 1. If _highWaterMark_ is *NaN* or _highWaterMark_ < *0*, throw a *RangeError* exception.

*+∞* is explicitly allowed as a valid high water mark . It causes backpressure to never be applied.

1. Return _highWaterMark_.

6.4.5. ValidateAndNormalizeQueuingStrategy ( size , highWaterMark )

1. If _size_ is not *undefined* and ! IsCallable(_size_) is *false*, throw a *TypeError* exception. 1. Let _highWaterMark_ be ? ValidateAndNormalizeHighWaterMark(_highWaterMark_). 1. Return Record {[[size]]: _size_, [[highWaterMark]]: _highWaterMark_}.

7. Global Properties

The following constructors must be exposed on the global object as data properties of the same name:

The attributes of these properties must be { [[Writable]]: true , [[Enumerable]]: false , [[Configurable]]: true }.

The ReadableStreamDefaultReader , ReadableStreamBYOBReader , ReadableStreamDefaultController , ReadableByteStreamController , WritableStreamDefaultWriter , and WritableStreamDefaultController classes are specifically not exposed, as they are not independently useful.

8. 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 or WritableStream constructors.

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

The following function creates readable streams that wrap WebSocket instances [HTML] , which are push sources that do not support backpressure signals. It illustrates how, when adapting a push source, usually most of the work happens in the start function.

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

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

8.3. 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 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 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);
            controller.byobRequest.respond(bytesRead);
          } else {
            const buffer = new ArrayBuffer(DEFAULT_CHUNK_SIZE);
            bytesRead = socket.readInto(buffer, 0, DEFAULT_CHUNK_SIZE);
            controller.enqueue(new Uint8Array(buffer, 0, bytesRead));
          }
          if (bytesRead === 0) {
            controller.close();
            return;
          }
          return readRepeatedly();
        });
      }
    },
    cancel() {
      socket.close();
    }
  });
}

ReadableStream instances returned from this function can now vend BYOB readers , with all of the aforementioned benefits and caveats.

8.4. 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 pull function, and not at startup time in the start function.

const fs = require("pr/fs"); // https://github.com/jden/pr
const CHUNK_SIZE = 1024;
function makeReadableFileStream(filename) {
  let fd;
  let position = 0;
  return new ReadableStream({
    start() {
      return fs.open(filename, "r").then(result => {
        fd = result;
      });
    },
    pull(controller) {
      const buffer = new ArrayBuffer(CHUNK_SIZE);
      return fs.read(fd, buffer, 0, CHUNK_SIZE, position).then(bytesRead => {
        if (bytesRead === 0) {
          return fs.close(fd).then(() => controller.close());
        } else {
          position += bytesRead;
          controller.enqueue(new Uint8Array(buffer, 0, bytesRead));
        }
      });
    },
    cancel() {
      return fs.close(fd);
    }
  });
}

We can then create and use readable streams for files just as we could before for sockets.

8.5. 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("pr/fs"); // https://github.com/jden/pr
const DEFAULT_CHUNK_SIZE = 1024;
function makeReadableByteFileStream(filename) {
  let fd;
  let position = 0;
  return new ReadableStream({
    type: "bytes",
    start() {
      return fs.open(filename, "r").then(result => {
        fd = result;
      });
    },
    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;
      return fs.read(fd, v.buffer, v.byteOffset, v.byteLength, position).then(bytesRead => {
        if (bytesRead === 0) {
          return fs.close(fd).then(() => controller.close());
        } else {
          position += bytesRead;
          controller.byobRequest.respond(bytesRead);
        }
      });
    },
    cancel() {
      return fs.close(fd);
    },
    autoAllocateChunkSize: DEFAULT_CHUNK_SIZE
  });
}

With this in hand, we can create and use BYOB readers for the returned ReadableStream . But we can also create default readers , using them in the same simple and generic manner as usual. The adaptation between the low-level byte tracking of the underlying byte source shown here, and the higher-level chunk-based consumption of a default reader , is all taken care of automatically by the streams implementation. The auto-allocation feature, via the autoAllocateChunkSize option, even allows us to write less code, compared to the manual branching in § 8.3 A readable byte stream with an underlying push source (no backpressure support) .

8.6. A writable stream with no backpressure or success signals

The following function returns a writable stream that wraps a WebSocket [HTML] . Web sockets do not provide any way to tell when a given chunk of data has been successfully sent (without awkward polling of 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 write() method and 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!"));
      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 new Promise((resolve, reject) => {
        ws.onclose = resolve;
        ws.close(1000);
      });
    },
    abort(reason) {
      return new Promise((resolve, reject) => {
        ws.onclose = resolve;
        ws.close(4000, reason && reason.message);
      });
    }
  });
}

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

8.7. 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("pr/fs"); // https://github.com/jden/pr
function makeWritableFileStream(filename) {
  let fd;
  return new WritableStream({
    start() {
      return fs.open(filename, "w").then(result => {
        fd = result;
      });
    },
    write(chunk) {
      return fs.write(fd, chunk, 0, chunk.length);
    },
    close() {
      return fs.close(fd);
    },
    abort() {
      return fs.close(fd);
    }
  });
}

We can then use this function to create a writable stream for a file, and write individual chunks of data to it:

const fileStream = makeWritableFileStream("/example/path/on/fs.txt");
const writer = fileStream.getWriter();
writer.write("To stream, or not to stream\n");
writer.write("That is the question\n");
writer.close()
  .then(() => console.log("chunks written and stream closed successfully!"))
  .catch(e => console.error(e));

Note that if a particular call to fs.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 ready getter, which is a signal to producers that they should back off and stop 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 fs.write , "it is unsafe to use fs.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 write method will not be called until any promises returned by previous calls have fulfilled!

8.8. 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 § 8.1 A readable stream with an underlying push source (no backpressure support) and § 8.6 A writable stream with no backpressure or success signals .

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.addEventListener("error", () => {
      controller.error(new Error("The WebSocket errored!"));
    });
    return new Promise(resolve => this._ws.onopen = resolve);
  }
  write(chunk) {
    this._ws.send(chunk);
  }
  close() {
    return new Promise((resolve, reject) => {
      this._ws.onclose = resolve;
      this._ws.close();
    });
  }
  abort(reason) {
    return new Promise((resolve, reject) => {
      ws.onclose = resolve;
      ws.close(4000, reason && reason.message);
    });
  }
}

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.

Conventions

This specification uses algorithm conventions very similar to those of [ECMASCRIPT] . However, it deviates in the following ways, mostly for brevity. It is hoped (and vaguely planned) that eventually the conventions of ECMAScript itself will evolve in these ways.

It’s also worth noting that, as in [ECMASCRIPT] , all numbers are represented as double-precision floating point values, and all arithmetic operations performed on them must be done in the standard way for such values.

Acknowledgments

The editor would like to thank Adam Rice, Anne van Kesteren, Ben Kelly, Brian di Palma, Calvin Metcalf, Dominic Tarr, Ed Hager, Forbes Lindesay, 贺师俊 (hax), isonmad, Jake Archibald, Jens Nockert, Mangala Sadhu Sangeet Singh Khalsa, Marcos Caceres, Marvin Hagemeister, Michael Mior, Mihai Potra, Simon Menke, Stephen Sugden, Tab Atkins, Tanguy Krotoff, Thorsten Lorenz, Till Schneidereit, Tim Caswell, Trevor Norris, tzik, Youenn Fablet, and Xabier Rodríguez for their contributions to this specification.

Special thanks to: Bert Belder for bringing up implementation concerns that led to crucial API changes; Forrest Norvell for his work on the initial reference implementation; Gorgi Kosev for his breakthrough idea of separating piping into two methods, thus resolving a major sticking point ; Isaac Schlueter for his pioneering work on JavaScript streams in Node.js; Jake Verbaten for his early involvement and support; Janessa Det for the logo; Will Chan for his help ensuring that the API allows high-performance network streaming; and 平野裕 (Yutaka Hirano) for his help with the readable stream reader design.

This standard is written by Domenic Denicola ( Google , d@domenic.me ) and 吉野剛史 (Takeshi Yoshino, Google , tyoshino@chromium.org ).

Per CC0 , to the extent possible under law, the editor has waived all copyright and related or neighboring rights to this work.

Intellectual property rights

Copyright © WHATWG (Apple, Google, Mozilla, Microsoft). This work is licensed under a Creative Commons Attribution 4.0 International License .

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[ECMASCRIPT]
ECMAScript Language Specification . URL: https://tc39.es/ecma262/
[FileAPI]
Marijn Kruisselbrink; Arun Ranganathan. File API . URL: https://w3c.github.io/FileAPI/
[HTML]
Anne van Kesteren; et al. HTML Standard . Living Standard. URL: https://html.spec.whatwg.org/multipage/
[PROMISES-GUIDE]
Domenic Denicola. Writing Promise-Using Specifications . 9 November 2018. TAG Finding. URL: https://www.w3.org/2001/tag/doc/promises-guide
[WebIDL]
Boris Zbarsky. Web IDL . URL: https://heycam.github.io/webidl/

Informative References

[FETCH]
Anne van Kesteren. Fetch Standard . Living Standard. URL: https://fetch.spec.whatwg.org/
[SERVICE-WORKERS]
Alex Russell; et al. Service Workers 1 . URL: https://w3c.github.io/ServiceWorker/