Using writable streams - Web APIs

Using writable streams
======================

As a JavaScript developer, programmatically writing data to a stream is very useful! This article explains the Streams API's writable stream functionality.

**Note:** This article assumes that you understand the use cases of writable streams, and are aware of the high-level concepts.
 If not, we suggest that you first read the Streams concepts and usage overview and dedicated Streams API concepts article, then come back.

**Note:** If you are looking for information about readable streams, try Using readable streams and Using readable byte streams instead.

Introducing an example
----------------------

In our dom-examples/streams repo you'll find a Simple writer example (see it live also). This takes a given message and writes it into a writable stream, displaying each chunk on the UI as it is written to the stream and also displaying the whole message on the UI when writing has finished.

How writable streams work
-------------------------

Let's look at how the writable stream functionality in our demo works.

### Constructing a writable stream

To create a writable stream, we use the `WritableStream()` constructor; the syntax looks complex at first, but actually isn't too bad.

The syntax skeleton looks like this:

```js
const stream = new WritableStream(
  {
    start(controller) {},
    write(chunk, controller) {},
    close(controller) {},
    abort(reason) {},
  },
  {
    highWaterMark: 3,
    size: () => 1,
  },
);
```

The constructor takes two objects as parameters. The first object is required, and creates a model in JavaScript of the underlying sink the data is being written to. The second object is optional, and allows you to specify a custom queueing strategy to use for your stream, which takes the form of an instance of `ByteLengthQueuingStrategy` or `CountQueuingStrategy`.

The first object can contain up to four members, all of which are optional:

1. `start(controller)` — A method that is called once, immediately after the `WritableStream` is constructed. Inside this method, you should include code that sets up the stream functionality, e.g. getting access to the underlying sink.
2. `write(chunk,controller)` — A method that is called repeatedly every time a new chunk is ready to be written to the underlying sink (specified in the `chunk` parameter).
3. `close(controller)` — A method that is called if the app signals that it has finished writing chunks to the stream. It should do whatever is necessary to finalize writes to the underlying sink, and release access to it.
4. `abort(reason)` — A method that will be called if the app signals that it wishes to abruptly close the stream and put it in an errored state.

The constructor call in our example looks like this:

```js
const decoder = new TextDecoder("utf-8");
const queuingStrategy = new CountQueuingStrategy({ highWaterMark: 1 });
let result = "";
const writableStream = new WritableStream(
  {
    // Implement the sink
    write(chunk) {
      return new Promise((resolve, reject) => {
        const buffer = new ArrayBuffer(1);
        const view = new Uint8Array(buffer);
        view[0] = chunk;
        const decoded = decoder.decode(view, { stream: true });
        const listItem = document.createElement("li");
        listItem.textContent = `Chunk decoded: ${decoded}`;
        list.appendChild(listItem);
        result += decoded;
        resolve();
      });
    },
    close() {
      const listItem = document.createElement("li");
      listItem.textContent = `[MESSAGE RECEIVED] ${result}`;
      list.appendChild(listItem);
    },
    abort(err) {
      console.error("Sink error:", err);
    },
  },
  queuingStrategy,
);
```

* The `write()` method contains a promise including code that decodes each written chunk into a format that can be written to the UI. This is called when each chunk is actually written (see the next section).
* The `close()` method is called automatically when writing has finished — it prints the entire decoded result to the UI in one string.
* The `abort()` method prints an error to the console if the stream is aborted.
### Writing

To actually write content to the stream we call the `sendMessage()` function, passing it a message to be written and the stream to write to:

```js
sendMessage("Hello, world.", writableStream);
```

The `sendMessage()` definition looks like so:

```js
function sendMessage(message, writableStream) {
  // defaultWriter is of type WritableStreamDefaultWriter
  const defaultWriter = writableStream.getWriter();
  const encoder = new TextEncoder();
  const encoded = encoder.encode(message, { stream: true });
  encoded.forEach((chunk) => {
    defaultWriter.ready
      .then(() => defaultWriter.write(chunk))
      .then(() => console.log("Chunk written to sink."))
      .catch((err) => console.error("Chunk error:", err));
  });
  // Call ready again to ensure that all chunks are written
  // before closing the writer.
  defaultWriter.ready
    .then(() => defaultWriter.close())
    .then(() => console.log("All chunks written"))
    .catch((err) => console.error("Stream error:", err));
}
```

So here we create a writer to write the chunks to the stream using `WritableStream.getWriter()`. This creates a `WritableStreamDefaultWriter` instance.

We also create a new `TextEncoder` instance using the relevant constructor to encode the message into chunks to be put into the stream.

With the chunks encoded, we then call `forEach()` on the resulting array. Inside this block we use `WritableStreamDefaultWriter.ready` to check whether the writer is ready to have another chunk written to it. `ready` returns a promise that fulfills when this is the case, inside of which we call `WritableStreamDefaultWriter.write()` to actually write the chunk to the stream. This also triggers the `write()` method specified inside the `WritableStream()` constructor, as discussed above.

After the chunks have all been written, we then perform the `ready` check once more, to check that the last chunk has finished being written and all the work is done. When this `ready` check fulfills, we invoke `WritableStreamDefaultWriter.close()` to close the stream. This also triggers the `close()` method specified inside the `WritableStream()` constructor, as discussed above.

### Controllers

As you'll have noticed when studying the `WritableStream()` syntax skeleton, the `start()`, `write()`, and `close()` methods can optionally have a `controller` parameter passed to them. This contains an instance of the `WritableStreamDefaultController` interface, which can be used by the developer to further control the stream as required.

This currently only has one method available on it — `WritableStreamDefaultController.error()`, which when invoked causes future interactions with the stream to error. This is useful when another part of an app goes wrong, and you want to propagate the error to the stream so that the whole system fails cleanly, rather than risking garbage being silently written to the stream (or something similarly bad).

### Closing and aborting

As mentioned above, we call the `close()` method when the writing is finished, which triggers the `close()` method specified inside the `WritableStream()` constructor.

We could also abort the stream by calling `WritableStreamDefaultWriter.abort()`.

The difference is that when close is called, any previously enqueued chunks are written and finished with before the stream is closed.

When abort is called, any previously enqueued chunks are just thrown away immediately and then the stream is moved to an errored state. This also triggers any `abort()` method specified in the `WritableStream()` constructor to be invoked.


Using readable byte streams - Web APIs

Using readable byte streams
===========================

 Readable *byte streams* are readable streams that have an underlying byte source of `type: "bytes"`, and which support efficient zero-copy transfer of data from the underlying source to a consumer (bypassing the stream's internal queues).
 They are intended for use cases where data might be supplied or requested in arbitrary sized and potentially very large chunks, and hence where avoiding making copies is likely to improve efficiency.

This article explains how readable byte streams compare to normal "default" streams, and how you create and consume them.

**Note:** Readable byte streams are almost identical to "normal" readable streams and almost all of the concepts are the same.
 This article assumes that you already understand those concepts and will only be covering them superficially (if at all).
 If you're not familiar with the relevant concepts, please first read: Using readable streams, Streams concepts and usage overview, and Streams API concepts.

Overview
--------

 Readable streams provides a consistent interface for streaming data from some underlying source, such as a file or socket, to a consumer, such as a reader, transform stream or writable stream.
 In a normal readable stream, data from the underlying source always passes to a consumer through the internal queues.
 A readable byte stream differs in that if the internal queues are empty, the underlying source can write directly to the consumer (an efficient zero-copy transfer).

 A readable byte stream is created by specifying `type: "bytes"` in the `underlyingSource` object that may be passed as the first parameter to the `ReadableStream()` constructor.
 With this value set, the stream is created with a `ReadableByteStreamController`, and this is the object that is passed to the underlying source when the `start(controller)` and `pull(controller)` callback functions are invoked.

 The main difference between `ReadableByteStreamController` and the default controller (`ReadableStreamDefaultController`) is that it has an additional property `ReadableByteStreamController.byobRequest` of type `ReadableStreamBYOBRequest`.
 This represents a pending read request by a consumer that will be made as a zero-copy transfer from the underlying source.
 The property will be `null` if there is no pending request.

A `byobRequest` is only made available when a read request is made on a readable byte stream and there is no data in the stream's internal queues (if there is data then the request is satisfied from those queues).

 An underlying byte source that needs to transfer data must check the `byobRequest` property and, if it is available, use it to transfer data.
 If the property is `null`, incoming data should instead be added to the stream's internal queues using `ReadableByteStreamController.enqueue()` (this is the only way to transfer data when using a "default" stream).

 The `ReadableStreamBYOBRequest` has a `view` property, which is a view on the buffer allocated for transfer.
 Data from an underlying source should be written into this property, and then the underlying source must call `respond()` indicating the number of bytes written.
 This signals that the data should be transferred, and the pending read request by the consumer resolved.
 After calling `respond()` the `view` can no longer be written.

 There is also an additional method `ReadableStreamBYOBRequest.respondWithNewView()` to which an underlying source can pass a "new" view containing data to be transferred.
 This new view must be over the *same* memory buffer as the original, and from the same starting offset.
 This method might be used if the underlying byte source needs to first transfer the view to a worker thread to populate (for example) and then get it back before responding to the `byobRequest`.
 In most cases this method will not be needed.

Readable byte streams are normally read using a `ReadableStreamBYOBReader`, which can be obtained by calling `ReadableStream.getReader()` on the stream, specifying `mode: "byob"` in the options parameter.

 A readable byte stream can also be read using a default reader (`ReadableStreamDefaultReader`), but in this case `byobRequest` objects are only created when automatic buffer allocation is enabled for the stream (`autoAllocateChunkSize` was set for the stream's `underlyingSource`).
 Note that the size indicated by `autoAllocateChunkSize` is used for the buffer size in this case; for a byte reader the buffer used is supplied by the consumer.
 If the property was not specified, the default reader will still "work" but the underlying source will never be offered a `byobRequest`, and all data will be transferred through the stream's internal queues.

Other than the differences outlined above, the controller and underlying source for bytes streams are very similar to those for default streams, and are used in much the same way.

Examples
--------

### Underlying push source with byte reader

This live example shows how to create a readable byte stream with a *push* underlying byte source, and read it using a byte reader.

 Unlike with a pull underlying byte source, data can arrive at any time.
 Therefore the underlying source must use `controller.byobRequest` to transfer incoming data if one exists, and otherwise enqueue the data into the stream's internal queues.
 Further, since the data can arrive at any time the monitoring behavior is set up in the `underlyingSource.start()` callback function.

 The example is highly influenced by a push byte source example in the stream specification.
 It uses a mocked "hypothetical socket" source that supplies data of arbitrary sizes.
 The reader is deliberately delayed at various points to allow the underlying source to use both transfer and enqueuing to send data to the stream.
 Backpressure support is not demonstrated.

**Note:** An underlying byte source can also be used with a default reader.
 If automatic buffer allocation is enabled the controller will supply fixed-size buffers for zero-copy transfers when there is an outstanding request from a reader and the stream's internal queues are empty.
 If automatic buffer allocation is not enabled then all data from the byte stream will always be enqueued.
 This is similar to the behavior shown in the "pull: underlying byte source examples.

#### Mocked underlying socket source

The mocked underlying source has three important methods:

* `select2()` represents an outstanding request on the socket.
 It returns a promise that is resolved when data is available.
* `readInto()` reads data from the socket into a supplied buffer and then clears the data.
* `close()` closes the socket.

 The implementation is very simplistic.
 As shown below, `select2()` creates a randomly sized buffer of random data on a timeout.
 The created data is read into a buffer then cleared in `readInto()`.

```js
class MockHypotheticalSocket {
  constructor() {
    this.max_data = 800; // total amount of data to stream from "socket"
    this.max_per_read = 100; // max data per read
    this.min_per_read = 40; // min data per read
    this.data_read = 0; // total data read so far (capped is maxdata)
    this.socketdata = null;
  }

  // Method returning promise when this socket is readable.
  select2() {
    // Object used to resolve promise
    const resultobj = {};
    resultobj["bytesRead"] = 0;

    return new Promise((resolve /\*, reject\*/) => {
      if (this.data_read >= this.max_data) {
        //out of data
        resolve(resultobj);
        return;
      }

      // Emulate slow read of data
      setTimeout(() => {
        const numberBytesReceived = this.getNumberRandomBytesSocket();
        this.data_read += numberBytesReceived;
        this.socketdata = this.randomByteArray(numberBytesReceived);
        resultobj["bytesRead"] = numberBytesReceived;
        resolve(resultobj);
      }, 500);
    });
  }

  /\* Read data into specified buffer offset \*/
  readInto(buffer, offset, length) {
    let length_data = 0;
    if (this.socketdata) {
      length_data = this.socketdata.length;
      const myview = new Uint8Array(buffer, offset, length);
      // Write the length of data specified into buffer
      // Code assumes buffer always bigger than incoming data
      for (let i = 0; i < length_data; i++) {
        myview[i] = this.socketdata[i];
      }
      this.socketdata = null; // Clear "socket" data after reading
    }
    return length_data;
  }

  // Dummy close function
  close() {
    return;
  }

  // Return random number bytes in this call of socket
  getNumberRandomBytesSocket() {
    // Capped to remaining data and the max min return-per-read range
    const remaining_data = this.max_data - this.data_read;
    const numberBytesReceived =
      remaining_data < this.min_per_read
        ? remaining_data
        : this.getRandomIntInclusive(
            this.min_per_read,
            Math.min(this.max_per_read, remaining_data),
          );
    return numberBytesReceived;
  }

  // Return random number between two values
  getRandomIntInclusive(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() \* (max - min + 1) + min);
  }

  // Return random character string
  randomChars(length = 8) {
    let string = "";
    let choices =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&\*()";

    for (let i = 0; i < length; i++) {
      string += choices.charAt(Math.floor(Math.random() \* choices.length));
    }
    return string;
  }

  /\* Return random Uint8Array of bytes \*/
  randomByteArray(bytes = 8) {
    const textEncoder = new TextEncoder();
    return textEncoder.encode(this.randomChars(bytes));
  }
}
```

```
.input {
  float: left;
  width: 50%;
}
.output {
  float: right;
  width: 50%;
  overflow-wrap: break-word;
}
button {
  display: block;
}
```

```
<button>Cancel stream</button>
<div class="input">
  <h2>Underlying source</h2>
  <ul></ul>
</div>
<div class="output">
  <h2>Consumer</h2>
  <ul></ul>
</div>
```

```
// Store reference to lists, paragraph and button
const list1 = document.querySelector(".input ul");
const list2 = document.querySelector(".output ul");
const button = document.querySelector("button");

// Create empty string in which to store final result
let result = "";

// Function to log data from underlying source
function logSource(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list1.appendChild(listItem);
}

// Function to log data from consumer
function logConsumer(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list2.appendChild(listItem);
}
```

#### Creating a readable socket push byte stream

The following code shows how to define a readable socket "push" byte stream.

 The `underlyingSource` object definition is passed as the first parameter to the `ReadableStream()` constructor.
 To make this a readable "byte" stream, we specify `type: "bytes"` as a property of the object.
 This ensures that the stream is handed a `ReadableByteStreamController` (instead of the default controller (`ReadableStreamDefaultController`))

 Since data can arrive at the socket before the consumer is ready to handle it, everything about reading the underlying source is configured in the `start()` callback method (we don't wait on a pull to start handling data).
 The implementation opens the "socket" and calls `select2()` to request data.
 When the returned promise resolves the code checks if `controller.byobRequest` exists (is not `null`), and if so calls `socket.readInto()` to copy data into the request and transfer it.
 If `byobRequest` does not exist there is no outstanding request from a consuming stream that can be satisfied as a zero-copy transfer.
 In this case, `controller.enqueue()` used to copy data to the stream internal queues.

 The `select2()` request for more data is reposted until a request is returned with no data.
 A this point the controller is used to close the stream.

```js
const stream = makeSocketStream("dummy host", "dummy port");

const DEFAULT\_CHUNK\_SIZE = 400;

function makeSocketStream(host, port) {
  const socket = new MockHypotheticalSocket();

  return new ReadableStream({
    type: "bytes",

    start(controller) {
      readRepeatedly().catch((e) => controller.error(e));
      function readRepeatedly() {
        return socket.select2().then(() => {
          // Since the socket can become readable even when there's
          // no pending BYOB requests, we need to handle both cases.
          let bytesRead;
          if (controller.byobRequest) {
            const v = controller.byobRequest.view;
            bytesRead = socket.readInto(v.buffer, v.byteOffset, v.byteLength);
            if (bytesRead === 0) {
              controller.close();
            }
            controller.byobRequest.respond(bytesRead);
            logSource(`byobRequest with ${bytesRead} bytes`);
          } else {
            const buffer = new ArrayBuffer(DEFAULT\_CHUNK\_SIZE);
            bytesRead = socket.readInto(buffer, 0, DEFAULT\_CHUNK\_SIZE);
            if (bytesRead === 0) {
              controller.close();
            } else {
              controller.enqueue(new Uint8Array(buffer, 0, bytesRead));
            }
            logSource(`enqueue() ${bytesRead} bytes (no byobRequest)`);
          }

          if (bytesRead === 0) {
            return;
            // no more bytes in source
          }
          return readRepeatedly();
        });
      }
    },

    cancel() {
      socket.close();
      logSource(`cancel(): socket closed`);
    },
  });
}
```

 Note that `readRepeatedly()` returns a promise, and we use this to catch any errors from setting up or handling the read operation.
 The errors are then passed to the controller as shown above (see `readRepeatedly().catch((e) => controller.error(e));`).

A `cancel()` method is provided at the end to close the underlying source; the `pull()` callback is not needed, and is therefore not implemented.

#### Consuming the push byte stream

 The following code creates a `ReadableStreamBYOBReader` for the socket byte stream and uses it read data into a buffer.
 Note `processText()` is called recursively to read more data until the buffer is filled.
 When the underlying source signals that it has no more data, the `reader.read()` will have `done` set to true, which in turn completes the read operation.

 This code is almost exactly the same as for the Underlying pull source with byte reader example above.
 The only difference is that the reader includes some code to slow down reading, so the log output can demonstrate that data will be enqueued if not read fast enough.

```js
const reader = stream.getReader({ mode: "byob" });
let buffer = new ArrayBuffer(4000);
readStream(reader);

function readStream(reader) {
  let bytesReceived = 0;
  let offset = 0;

  while (offset < buffer.byteLength) {
    // read() returns a promise that resolves when a value has been received
    reader
      .read(new Uint8Array(buffer, offset, buffer.byteLength - offset))
      .then(async function processText({ done, value }) {
        // Result objects contain two properties:
        // done - true if the stream has already given all its data.
        // value - some data. Always undefined when done is true.

        if (done) {
          logConsumer(`readStream() complete. Total bytes: ${bytesReceived}`);
          return;
        }

        buffer = value.buffer;
        offset += value.byteLength;
        bytesReceived += value.byteLength;

        //logConsumer(`Read ${bytesReceived} bytes: ${value}`);
        logConsumer(`Read ${bytesReceived} bytes`);
        result += value;

        // Add delay to emulate when data can't be read and data is enqueued
        if (bytesReceived > 300 && bytesReceived < 600) {
          logConsumer(`Delaying read to emulate slow stream reading`);
          const delay = (ms) =>
            new Promise((resolve) => setTimeout(resolve, ms));
          await delay(1000);
        }

        // Read some more, and call this function again
        return reader
          .read(new Uint8Array(buffer, offset, buffer.byteLength - offset))
          .then(processText);
      });
  }
}
```

#### Cancelling the stream using the reader

 We can use `ReadableStreamBYOBReader.cancel()` to cancel the stream.
 For this example we call the method if a button is clicked with a reason "user choice" (other HTML and code for the button not shown).
 We also log when the cancel operation completes.

```js
button.addEventListener("click", () => {
  reader
    .cancel("user choice")
    .then(() => logConsumer("reader.cancel complete"));
});
```

`ReadableStreamBYOBReader.releaseLock()` can be used to release the reader without cancelling the stream.
 Note however that any outstanding read requests will immediately be rejected.
 A new reader can be acquired later on to read the remaining chunks.

#### Monitoring for stream for close/error

 The `ReadableStreamBYOBReader.closed` property returns a promise that will resolve when the stream is closed, and reject if there is an error.
 While no errors are expected in this case, the following code should log the completion case.

```js
reader.closed
  .then(() => {
    logConsumer("ReadableStreamBYOBReader.closed: resolved");
  })
  .catch(() => {
    logConsumer("ReadableStreamBYOBReader.closed: rejected:");
  });
```

#### Result

 The logging from the underlying push source (left) and consumer (right) are shown below.
 Not the period in the middle where data is enqueued rather than transferred as a zero-copy operation.

### Underlying pull source with byte reader

This live example shows how data might be read from an "pull" underlying byte source, such as a file, and transferred by a stream as a zero-copy transfer to a `ReadableStreamBYOBReader`.

#### Mocked underlying file source

 For the underlying pull source we use the following class to (*very* superficially) mock a nodejs `FileHandle`, and in particular the `read()` method.
 The class generates random data to represent a file.
 The `read()` method reads a "semi-random" sized block of random data into a provided buffer from the specified position.
 The `close()` method does nothing: it is only provided to show where you might close the source when defining the constructor for the stream.

**Note:** A similar class is used for all the "pull source" examples.
 It is shown here for information only (so that it is obvious that it is a mock).

```js
class MockUnderlyingFileHandle {
  constructor() {
    this.maxdata = 100; // "file size"
    this.maxReadChunk = 25; // "max read chunk size"
    this.minReadChunk = 13; // "min read chunk size"
    this.filedata = this.randomByteArray(this.maxdata);
    this.position = 0;
  }

  // Read data from "file" at position/length into specified buffer offset
  read(buffer, offset, length, position) {
    // Object used to resolve promise
    const resultobj = {};
    resultobj["buffer"] = buffer;
    resultobj["bytesRead"] = 0;

    return new Promise((resolve /\*, reject\*/) => {
      if (position >= this.maxdata) {
        //out of data
        resolve(resultobj);
        return;
      }

      // Simulate a file read that returns random numbers of bytes
      // Read minimum of bytes requested and random bytes that can be returned
      let readLength =
        Math.floor(
          Math.random() \* (this.maxReadChunk - this.minReadChunk + 1),
        ) + this.minReadChunk;
      readLength = length > readLength ? readLength : length;

      // Read random data into supplied buffer
      const myview = new Uint8Array(buffer, offset, readLength);
      // Write the length of data specified
      for (let i = 0; i < readLength; i++) {
        myview[i] = this.filedata[position + i];
        resultobj["bytesRead"] = i + 1;
        if (position + i + 1 >= this.maxdata) {
          break;
        }
      }
      // Emulate slow read of data
      setTimeout(() => {
        resolve(resultobj);
      }, 1000);
    });
  }

  // Dummy close function
  close() {
    return;
  }

  // Return random character string
  randomChars(length = 8) {
    let string = "";
    let choices =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&\*()";

    for (let i = 0; i < length; i++) {
      string += choices.charAt(Math.floor(Math.random() \* choices.length));
    }
    return string;
  }

  // Return random Uint8Array of bytes
  randomByteArray(bytes = 8) {
    const textEncoder = new TextEncoder();
    return textEncoder.encode(this.randomChars(bytes));
  }
}
```

```
.input {
  float: left;
  width: 50%;
}
.output {
  float: right;
  width: 50%;
  overflow-wrap: break-word;
}
button {
  display: block;
}
```

```
<button>Cancel stream</button>
<div class="input">
  <h2>Underlying source</h2>
  <ul></ul>
</div>
<div class="output">
  <h2>Consumer</h2>
  <ul></ul>
</div>
```

```
// Store reference to lists, paragraph and button
const list1 = document.querySelector(".input ul");
const list2 = document.querySelector(".output ul");
const button = document.querySelector("button");

// Create empty string in which to store final result
let result = "";

// Function to log data from underlying source
function logSource(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list1.appendChild(listItem);
}

// Function to log data from consumer
function logConsumer(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list2.appendChild(listItem);
}
```

#### Creating a readable file byte stream

The following code shows how to define a readable file byte stream.

 Just as for the previous example, the `underlyingSource` object definition is passed as the first parameter to the `ReadableStream()` constructor.
 To make this a readable "byte" stream, we specify `type: "bytes"` as a property of the object.
 This ensures that the stream is handed a `ReadableByteStreamController`.

 The `start()` function simply opens the file handle, which is then closed in the `cancel()` callback.
 `cancel()` is provided to clean up any resources if `ReadableStream.cancel()` or `ReadableStreamDefaultController.close()` are called.

 Most of the interesting code is in the `pull()` callback.
 This copies data from the file into the pending read request (`ReadableByteStreamController.byobRequest`) and then calls `respond()` to indicate how much data is in the buffer and transfer it.
 If 0 bytes were transferred from the file then we know it has all been copied, and call `close()` on the controller, which in turn will result in `cancel()` being called on the underlying source.

```js
const stream = makeReadableByteFileStream("dummy file.txt");

function makeReadableByteFileStream(filename) {
  let fileHandle;
  let position = 0;
  return new ReadableStream({
    type: "bytes", // An underlying byte stream!
    start(controller) {
      // Called to initialise the underlying source.
      // For a file source open a file handle (here we just create the mocked object).
      fileHandle = new MockUnderlyingFileHandle();
      logSource(
        `start(): ${controller.constructor.name}.byobRequest = ${controller.byobRequest}`,
      );
    },
    async pull(controller) {
      // Called when there is a pull request for data
      const theView = controller.byobRequest.view;
      const { bytesRead, buffer } = await fileHandle.read(
        theView.buffer,
        theView.byteOffset,
        theView.byteLength,
        position,
      );
      if (bytesRead === 0) {
        await fileHandle.close();
        controller.close();
        controller.byobRequest.respond(0);
        logSource(
          `pull() with byobRequest. Close controller (read bytes: ${bytesRead})`,
        );
      } else {
        position += bytesRead;
        controller.byobRequest.respond(bytesRead);
        logSource(`pull() with byobRequest. Transfer ${bytesRead} bytes`);
      }
    },
    cancel(reason) {
      // This is called if the stream is cancelled (via reader or controller).
      // Clean up any resources
      fileHandle.close();
      logSource(`cancel() with reason: ${reason}`);
    },
  });
}
```

#### Consuming the byte stream

 The following code creates a `ReadableStreamBYOBReader` for the file byte stream and uses it read data into a buffer.
 Note `processText()` is called recursively to read more data until the buffer is filled.
 When the underlying source signals that it has no more data, the `reader.read()` will have `done` set to true, which in turn completes the read operation.

```js
const reader = stream.getReader({ mode: "byob" });
let buffer = new ArrayBuffer(200);
readStream(reader);

function readStream(reader) {
  let bytesReceived = 0;
  let offset = 0;

  // read() returns a promise that resolves when a value has been received
  reader
    .read(new Uint8Array(buffer, offset, buffer.byteLength - offset))
    .then(function processText({ done, value }) {
      // Result objects contain two properties:
      // done - true if the stream has already given all its data.
      // value - some data. Always undefined when done is true.

      if (done) {
        logConsumer(`readStream() complete. Total bytes: ${bytesReceived}`);
        return;
      }

      buffer = value.buffer;
      offset += value.byteLength;
      bytesReceived += value.byteLength;

      logConsumer(
        `Read ${value.byteLength} (${bytesReceived}) bytes: ${value}`,
      );
      result += value;

      // Read some more, and call this function again
      return reader
        .read(new Uint8Array(buffer, offset, buffer.byteLength - offset))
        .then(processText);
    });
}
```

Lastly, we add a handler that will cancel the stream if a button is clicked (other HTML and code for the button not shown).

```js
button.addEventListener("click", () => {
  reader.cancel("user choice").then(() => {
    logConsumer(`reader.cancel complete`);
  });
});
```

#### Result

 The logging from the underlying pull source (left) and consumer (right) are shown below.
 Of particular note are that the:

* `start()` function is passed a `ReadableByteStreamController`
* the buffer passed to the reader is large enough to encompass the whole "file".
 The underlying data source supplies the data in random-sized chunks.

### Underlying pull source with default reader

 This live example shows how the same data might be read as a zero-copy transfer using a default reader (`ReadableStreamDefaultReader`).
 This uses the same mocked underlying file source as in the preceding example.
```

class MockUnderlyingFileHandle {
  constructor() {
    this.maxdata = 100; // "file size"
    this.maxReadChunk = 25; // "max read chunk size"
    this.minReadChunk = 13; // "min read chunk size"
    this.filedata = this.randomByteArray(this.maxdata);
    this.position = 0;
  }

  // Read data from "file" at position/length into specified buffer offset
  read(buffer, offset, length, position) {
    // Object used to resolve promise
    const resultobj = {};
    resultobj["buffer"] = buffer;
    resultobj["bytesRead"] = 0;

    return new Promise((resolve /\*, reject\*/) => {
      if (position >= this.maxdata) {
        //out of data
        resolve(resultobj);
        return;
      }

      // Simulate a file read that returns random numbers of bytes
      // Read minimum of bytes requested and random bytes that can be returned
      let readLength =
        Math.floor(
          Math.random() \* (this.maxReadChunk - this.minReadChunk + 1),
        ) + this.minReadChunk;
      readLength = length > readLength ? readLength : length;

      // Read random data into supplied buffer
      const myview = new Uint8Array(buffer, offset, readLength);
      // Write the length of data specified
      for (let i = 0; i < readLength; i++) {
        myview[i] = this.filedata[position + i];
        resultobj["bytesRead"] = i + 1;
        if (position + i + 1 >= this.maxdata) {
          break;
        }
      }
      // Emulate slow read of data
      setTimeout(() => {
        resolve(resultobj);
      }, 1000);
    });
  }

  // Dummy close function
  close() {
    return;
  }

  // Return random character string
  randomChars(length = 8) {
    let string = "";
    let choices =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&\*()";

    for (let i = 0; i < length; i++) {
      string += choices.charAt(Math.floor(Math.random() \* choices.length));
    }
    return string;
  }

  // Return random Uint8Array of bytes
  randomByteArray(bytes = 8) {
    const textEncoder = new TextEncoder();
    return textEncoder.encode(this.randomChars(bytes));
  }
}
```

```
.input {
  float: left;
  width: 50%;
}
.output {
  float: right;
  width: 50%;
  overflow-wrap: break-word;
}
button {
  display: block;
}
```

```
<button>Cancel stream</button>
<div class="input">
  <h2>Underlying source</h2>
  <ul></ul>
</div>
<div class="output">
  <h2>Consumer</h2>
  <ul></ul>
</div>
```

```
// Store reference to lists, paragraph and button
const list1 = document.querySelector(".input ul");
const list2 = document.querySelector(".output ul");
const button = document.querySelector("button");

// Create empty string in which to store final result
let result = "";

// Function to log data from underlying source
function logSource(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list1.appendChild(listItem);
}

// Function to log data from consumer
function logConsumer(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list2.appendChild(listItem);
}
```

#### Creating a readable file byte stream with automatic buffer allocation

The only difference in our underlying source is that we must specify `autoAllocateChunkSize`, and that the size will be used as the view buffer size for `controller.byobRequest`, rather than one supplied by the consumer.

```js
const DEFAULT\_CHUNK\_SIZE = 20;
const stream = makeReadableByteFileStream("dummy file.txt");

function makeReadableByteFileStream(filename) {
  let fileHandle;
  let position = 0;
  return new ReadableStream({
    type: "bytes", // An underlying byte stream!
    start(controller) {
      // Called to initialise the underlying source.
      // For a file source open a file handle (here we just create the mocked object).
      fileHandle = new MockUnderlyingFileHandle();
      logSource(
        `start(): ${controller.constructor.name}.byobRequest = ${controller.byobRequest}`,
      );
    },
    async pull(controller) {
      // Called when there is a pull request for data
      const theView = controller.byobRequest.view;
      const { bytesRead, buffer } = await fileHandle.read(
        theView.buffer,
        theView.byteOffset,
        theView.byteLength,
        position,
      );
      if (bytesRead === 0) {
        await fileHandle.close();
        controller.close();
        controller.byobRequest.respond(0);
        logSource(
          `pull() with byobRequest. Close controller (read bytes: ${bytesRead})`,
        );
      } else {
        position += bytesRead;
        controller.byobRequest.respond(bytesRead);
        logSource(`pull() with byobRequest. Transfer ${bytesRead} bytes`);
      }
    },
    cancel(reason) {
      // This is called if the stream is cancelled (via reader or controller).
      // Clean up any resources
      fileHandle.close();
      logSource(`cancel() with reason: ${reason}`);
    },
    autoAllocateChunkSize: DEFAULT\_CHUNK\_SIZE, // Only relevant if using a default reader
  });
}
```

#### Consuming the byte stream with default reader

 The following code creates a `ReadableStreamDefaultReader` for the file byte stream by calling `stream.getReader();` without specifying the mode, and uses it read data into a buffer.
 The operation of the code is the same as the previous example except that the buffer is supplied by the stream rather than the consumer.

```js
const reader = stream.getReader();
readStream(reader);

function readStream(reader) {
  let bytesReceived = 0;
  let result = "";

  // read() returns a promise that resolves
  // when a value has been received
  reader.read().then(function processText({ done, value }) {
    // Result objects contain two properties:
    // done - true if the stream has already given you all its data.
    // value - some data. Always undefined when done is true.
    if (done) {
      logConsumer(`readStream() complete. Total bytes: ${bytesReceived}`);
      return;
    }

    bytesReceived += value.length;
    logConsumer(
      `Read ${value.length} (${bytesReceived}). Current bytes = ${value}`,
    );
    result += value;

    // Read some more, and call this function again
    return reader.read().then(processText);
  });
}
```

Lastly, we add a handler that will cancel the stream if a button is clicked (other HTML and code for the button not shown).

```js
button.addEventListener("click", () => {
  reader.cancel("user choice").then(() => {
    logConsumer(`reader.cancel complete`);
  });
});
```

#### Result

The logging from the underlying byte pull source (left) and consumer (right) are shown below.

 Note that the chunks are now *at most* 20-byte wide, as this is the size of the auto allocated buffer specified in the underlying byte source (`autoAllocateChunkSize`).
 These are made as zero-copy transfers.

### Underlying pull source with default reader and no allocation

For completeness, we can also use a default reader with a byte source that does not support automatic buffer allocation.
```

class MockUnderlyingFileHandle {
  constructor() {
    this.maxdata = 100; // "file size"
    this.maxReadChunk = 25; // "max read chunk size"
    this.minReadChunk = 13; // "min read chunk size"
    this.filedata = this.randomByteArray(this.maxdata);
    this.position = 0;
  }

  // Read data from "file" at position/length into specified buffer offset
  read(buffer, offset, length, position) {
    // Object used to resolve promise
    const resultobj = {};
    resultobj["buffer"] = buffer;
    resultobj["bytesRead"] = 0;

    return new Promise((resolve /\*, reject\*/) => {
      if (position >= this.maxdata) {
        //out of data
        resolve(resultobj);
        return;
      }

      // Simulate a file read that returns random numbers of bytes
      // Read minimum of bytes requested and random bytes that can be returned
      let readLength =
        Math.floor(
          Math.random() \* (this.maxReadChunk - this.minReadChunk + 1),
        ) + this.minReadChunk;
      readLength = length > readLength ? readLength : length;

      // Read random data into supplied buffer
      const myview = new Uint8Array(buffer, offset, readLength);
      // Write the length of data specified
      for (let i = 0; i < readLength; i++) {
        myview[i] = this.filedata[position + i];
        resultobj["bytesRead"] = i + 1;
        if (position + i + 1 >= this.maxdata) {
          break;
        }
      }
      // Emulate slow read of data
      setTimeout(() => {
        resolve(resultobj);
      }, 1000);
    });
  }

  // Dummy close function
  close() {
    return;
  }

  // Return random character string
  randomChars(length = 8) {
    let string = "";
    let choices =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&\*()";

    for (let i = 0; i < length; i++) {
      string += choices.charAt(Math.floor(Math.random() \* choices.length));
    }
    return string;
  }

  // Return random Uint8Array of bytes
  randomByteArray(bytes = 8) {
    const textEncoder = new TextEncoder();
    return textEncoder.encode(this.randomChars(bytes));
  }
}
```

```
.input {
  float: left;
  width: 50%;
}
.output {
  float: right;
  width: 50%;
  overflow-wrap: break-word;
}
button {
  display: block;
}
```

```
<button>Cancel stream</button>
<div class="input">
  <h2>Underlying source</h2>
  <ul></ul>
</div>
<div class="output">
  <h2>Consumer</h2>
  <ul></ul>
</div>
```

```
// Store reference to lists, paragraph and button
const list1 = document.querySelector(".input ul");
const list2 = document.querySelector(".output ul");
const button = document.querySelector("button");

// Create empty string in which to store final result
let result = "";

// Function to log data from underlying source
function logSource(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list1.appendChild(listItem);
}

// Function to log data from consumer
function logConsumer(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list2.appendChild(listItem);
}
```

 However in this case the controller will not supply a `byobRequest` for the underlying source to write into.
 Instead the underlying source would have to enqueue the data.
 Note below that to support this case, in `pull()` we need to check if the `byobRequest` exists.

```js
const stream = makeReadableByteFileStream("dummy file.txt");
const DEFAULT\_CHUNK\_SIZE = 40;

function makeReadableByteFileStream(filename) {
  let fileHandle;
  let position = 0;
  return new ReadableStream({
    type: "bytes", // An underlying byte stream!
    start(controller) {
      // Called to initialise the underlying source.
      // For a file source open a file handle (here we just create the mocked object).
      fileHandle = new MockUnderlyingFileHandle();
      logSource(
        `start(): ${controller.constructor.name}.byobRequest = ${controller.byobRequest}`,
      );
    },
    async pull(controller) {
      // Called when there is a pull request for data
      if (controller.byobRequest) {
        const theView = controller.byobRequest.view;
        const { bytesRead, buffer } = await fileHandle.read(
          theView.buffer,
          theView.byteOffset,
          theView.byteLength,
          position,
        );
        if (bytesRead === 0) {
          await fileHandle.close();
          controller.close();
          controller.byobRequest.respond(0);
          logSource(
            `pull() with byobRequest. Close controller (read bytes: ${bytesRead})`,
          );
        } else {
          position += bytesRead;
          controller.byobRequest.respond(bytesRead);
          logSource(`pull() with byobRequest. Transfer ${bytesRead} bytes`);
        }
      } else {
        // No BYOBRequest so enqueue data to stream
        // NOTE, this branch would only execute for a default reader if autoAllocateChunkSize is not defined.
        const mynewBuffer = new Uint8Array(DEFAULT\_CHUNK\_SIZE);
        const { bytesRead, buffer } = await fileHandle.read(
          mynewBuffer.buffer,
          mynewBuffer.byteOffset,
          mynewBuffer.byteLength,
          position,
        );
        if (bytesRead === 0) {
          await fileHandle.close();
          controller.close();
          controller.enqueue(mynewBuffer);
          logSource(
            `pull() with no byobRequest. Close controller (read bytes: ${bytesRead})`,
          );
        } else {
          position += bytesRead;
          controller.enqueue(mynewBuffer);
          logSource(`pull() with no byobRequest. enqueue() ${bytesRead} bytes`);
        }
      }
    },
    cancel(reason) {
      // This is called if the stream is cancelled (via reader or controller).
      // Clean up any resources
      fileHandle.close();
      logSource(`cancel() with reason: ${reason}`);
    },
  });
}
```

```
const reader = stream.getReader();

readStream(reader);

function readStream(reader) {
  let bytesReceived = 0;
  let result = "";

  // read() returns a promise that resolves
  // when a value has been received
  reader.read().then(function processText({ done, value }) {
    // Result objects contain two properties:
    // done - true if the stream has already given you all its data.
    // value - some data. Always undefined when done is true.
    if (done) {
      logConsumer(`readStream() complete. Total bytes: ${bytesReceived}`);
      return;
    }

    bytesReceived += value.length;
    logConsumer(`Read ${bytesReceived} bytes so far. Current bytes = ${value}`);
    result += value;

    // Read some more, and call this function again
    return reader.read().then(processText);
  });
}
```

```
button.addEventListener("click", () => {
  reader.cancel("user choice").then(() => {
    logConsumer(`reader.cancel complete`);
  });
});
```

#### Result

 The logging from the underlying pull source (left) and consumer (right) are shown below.
 Note that the underlying source side shows that the data has been enqueued rather than zero-byte transferred.

See also
--------

* Streams API concepts
* Streams concepts and usage overview
* Using readable streams


Using readable streams - Web APIs

Using readable streams
======================

As a JavaScript developer, programmatically reading and manipulating streams of data received over the network, chunk by chunk, is very useful! But how do you use the Streams API's readable stream functionality? This article explains the basics.

**Note:** This article assumes that you understand the use cases of readable streams, and are aware of the high-level concepts. If not, we suggest that you first read the Streams concepts and usage overview and dedicated Streams API concepts article, then come back.

**Note:** If you are looking for information on writable streams try Using writable streams instead.

Finding some examples
---------------------

We will look at various examples in this article, taken from our dom-examples/streams repo. You can find the full source code there, as well as links to the examples.

Consuming a fetch as a stream
-----------------------------

The Fetch API allows you to fetch resources across the network, providing a modern alternative to XHR. It has a number of advantages, and what is really nice about it is that browsers have recently added the ability to consume a fetch response as a readable stream.

The `Request.body` and `Response.body` properties are available, which are getters exposing the body contents as a readable stream.

As our Simple stream pump example shows (see it live also), exposing it is a matter of just accessing the `body` property of the response:

```js
// Fetch the original image
fetch("./tortoise.png")
  // Retrieve its body as ReadableStream
  .then((response) => response.body);
```

This provides us with a `ReadableStream` object.

### Attaching a reader

Now we've got our streaming body, reading the stream requires attaching a reader to it. This is done using the `ReadableStream.getReader()` method:

```js
// Fetch the original image
fetch("./tortoise.png")
  // Retrieve its body as ReadableStream
  .then((response) => response.body)
  .then((body) => {
    const reader = body.getReader();
    // …
  });
```

Invoking this method creates a reader and locks it to the stream — no other reader may read this stream until this reader is released, e.g. by invoking `ReadableStreamDefaultReader.releaseLock()`.

Also note that the previous example can be reduced by one step, as `response.body` is synchronous and so doesn't need the promise:

```js
// Fetch the original image
fetch("./tortoise.png")
  // Retrieve its body as ReadableStream
  .then((response) => {
    const reader = response.body.getReader();
    // …
  });
```

### Reading the stream

Now you've got your reader attached, you can read data chunks out of the stream using the `ReadableStreamDefaultReader.read()` method. This reads one chunk out of the stream, which you can then do anything you like with. For example, our Simple stream pump example goes on to enqueue each chunk in a new, custom `ReadableStream` (we will find more about this in the next section), then create a new `Response` out of it, consume it as a `Blob`, create an object URL out of that blob using `URL.createObjectURL()`, and then display it on screen in an `<img>` element, effectively creating a copy of the image we originally fetched.

```js
// Fetch the original image
fetch("./tortoise.png")
  // Retrieve its body as ReadableStream
  .then((response) => {
    const reader = response.body.getReader();
    return new ReadableStream({
      start(controller) {
        return pump();
        function pump() {
          return reader.read().then(({ done, value }) => {
            // When no more data needs to be consumed, close the stream
            if (done) {
              controller.close();
              return;
            }
            // Enqueue the next data chunk into our target stream
            controller.enqueue(value);
            return pump();
          });
        }
      },
    });
  })
  // Create a new response out of the stream
  .then((stream) => new Response(stream))
  // Create an object URL for the response
  .then((response) => response.blob())
  .then((blob) => URL.createObjectURL(blob))
  // Update image
  .then((url) => console.log((image.src = url)))
  .catch((err) => console.error(err));
```

Let's look in detail at how `read()` is used. In the `pump()` function seen above we first invoke `read()`, which returns a promise containing a results object — this has the results of our read in it, in the form `{ done, value }`:

```js
reader.read().then(({ done, value }) => {
  /\* … \*/
});
```

The results can be one of three different types:

* If a chunk is available to read, the promise will be fulfilled with an object of the form `{ value: theChunk, done: false }`.
* If the stream becomes closed, the promise will be fulfilled with an object of the form `{ value: undefined, done: true }`.
* If the stream becomes errored, the promise will be rejected with the relevant error.

Next, we check whether `done` is `true`. If so, there are no more chunks to read (the value is `undefined`) so we return out of the function and close the custom stream with `ReadableStreamDefaultController.close()`:

```js
if (done) {
  controller.close();
  return;
}
```

**Note:** `close()` is part of the new custom stream, not the original stream we are discussing here. We'll explain more about the custom stream in the next section.

If `done` is not `true`, we process the new chunk we've read (contained in the `value` property of the results object) and then call the `pump()` function again to read the next chunk.

```js
// Enqueue the next data chunk into our target stream
controller.enqueue(value);
return pump();
```

This is the standard pattern you'll see when using stream readers:

1. You write a function that starts off by reading the stream.
2. If there is no more stream to read, you return out of the function.
3. If there is more stream to read, you process the current chunk then run the function again.
4. You keep chaining the `pipe` function until there is no more stream to read, in which case step 2 is followed.

Removing all the code to actually perform a "pump", the code might be generalized to something like this:

```js
fetch("http://example.com/somefile.txt")
  // Retrieve its body as ReadableStream
  .then((response) => {
    const reader = response.body.getReader();
    // read() returns a promise that resolves when a value has been received
    reader.read().then(function pump({ done, value }) {
      if (done) {
        // Do something with last chunk of data then exit reader
        return;
      }
      // Otherwise do something here to process current chunk

      // Read some more, and call this function again
      return reader.read().then(pump);
    });
  })
  .catch((err) => console.error(err));
```

**Note:** The function looks as if `pump()` calls itself and leads to a potentially deep recursion.
 However, because `pump` is asynchronous and each `pump()` call is at the end of the promise handler, it's actually analogous to a chain of promise handlers.

Reading the stream is even easier when written using async/await rather than promises:

```js
async function readData(url) {
  const response = await fetch(url);
  const reader = response.body.getReader();
  while (true) {
    const { done, value } = await reader.read();
    if (done) {
      // Do something with last chunk of data then exit reader
      return;
    }
    // Otherwise do something here to process current chunk
  }
}
```

Consuming a fetch() using asynchronous iteration
------------------------------------------------

 There is another even simpler way to consume a `fetch()`, which is to iterate the returned `response.body` using the `for await...of` syntax.
 This works because the `response.body` returns a `ReadableStream`, which is an async iterable.

Using this approach, the example code in the previous section can be rewritten as shown:

```js
async function readData(url) {
  const response = await fetch(url);
  for await (const chunk of response.body) {
    // Do something with each "chunk"
  }
  // Exit when done
}
```

If you want to stop iterating the stream you can cancel the `fetch()` operation using an `AbortController` and its associated `AbortSignal`:

```js
const aborter = new AbortController();
button.addEventListener("click", () => aborter.abort());
logChunks("http://example.com/somefile.txt", { signal: aborter.signal });

async function logChunks(url, { signal }) {
  const response = await fetch(url, { signal });
  for await (const chunk of response.body) {
    // Do something with the chunk
  }
}
```

 Alternatively, you can exit the loop using `break`, as shown in the code below.
 Note that code in the loop is only run when the stream has new data to process, so there may be some delay between the signal being aborted and `break` being called.

```js
const aborter = new AbortController();
button.addEventListener("click", () => aborter.abort());
logChunks("http://example.com/somefile.txt", { signal: aborter.signal });

async function logChunks(url, { signal }) {
  const response = await fetch(url);
  for await (const chunk of response.body) {
    if (signal.aborted) break; // just break out of loop
    // Do something with the chunk
  }
}
```

### Example async reader
```

// A mock push source.
// Used to simulate some random data arriving
class MockPushSource {
  // total amount of data to stream from the push source
  static #maxData = 90;
  // total data read so far (capped to maxData)
  #dataRead = 0;

  // Method returning promise when this push source is readable.
  dataRequest() {
    const result = {
      bytesRead: 8,
      data: "",
    };

    return new Promise((resolve) => {
      if (this.#dataRead >= MockPushSource.#maxData) {
        // Out of data
        result.bytesRead = 0;
        result.data = "";
        resolve(result);
        return;
      }

      // Emulate slow read of data
      setTimeout(() => {
        const numberBytesReceived = 8;
        this.#dataRead += numberBytesReceived;
        result.data = MockPushSource.#randomChars();
        resolve(result);
      }, 500);
    });
  }

  // Dummy close function
  close() {
    return;
  }

  // Return random character string
  static #randomChars(length = 8) {
    let string = "";
    const choices =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&\*()";

    for (let i = 0; i < length; i++) {
      string += choices[Math.floor(Math.random() \* choices.length)];
    }
    return string;
  }
}
```

```
.input {
  float: left;
  width: 50%;
}
.output {
  float: right;
  width: 50%;
  overflow-wrap: break-word;
}
button {
  display: block;
}
```

```
<button>Cancel stream</button>
<div class="input">
  <h2>Underlying source</h2>
  <ul></ul>
</div>
<div class="output">
  <h2>Consumer</h2>
  <ul></ul>
</div>
```

```
// Store reference to lists, paragraph and button
const list1 = document.querySelector(".input ul");
const list2 = document.querySelector(".output ul");
const button = document.querySelector("button");

// Create empty string in which to store final result
let result = "";

// Function to log data from underlying source
function logSource(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list1.appendChild(listItem);
}

// Function to log data from consumer
function logConsumer(result) {
  const listItem = document.createElement("li");
  listItem.textContent = result;
  list2.appendChild(listItem);
}
```

```
const stream = makePushSourceStream();

function makePushSourceStream() {
  const pushSource = new MockPushSource();

  return new ReadableStream({
    start(controller) {
      readRepeatedly().catch((e) => controller.error(e));
      function readRepeatedly() {
        return pushSource.dataRequest().then((result) => {
          if (result.data.length == 0) {
            logSource(`No data from source: closing`);
            controller.close();
            return;
          }

          logSource(`Enqueue data: ${result.data}`);
          controller.enqueue(result.data);
          return readRepeatedly();
        });
      }
    },

    cancel() {
      logSource(`cancel() called on underlying source`);
      pushSource.close();
    },
  });
}
```

```
// Monkey patch fetch() so it returns a response that is a mocked stream
window.fetch = async (...args) => {
  return { body: stream };
};
```

 The code below shows a more complete example.
 Here the fetch stream is consumed using the iterator inside a try/catch block.
 On each iteration of the loop the code simply logs and counts the received bytes.
 If there is an error it logs the issue.
 The `fetch()` operation can be cancelled using an `AbortSignal`, which would also be logged as an error.

```js
let bytes = 0;

const aborter = new AbortController();
button.addEventListener("click", () => aborter.abort());
logChunks("http://example.com/somefile.txt", { signal: aborter.signal });

async function logChunks(url, { signal }) {
  try {
    const response = await fetch(url, signal);
    for await (const chunk of response.body) {
      if (signal.aborted) throw signal.reason;
      bytes += chunk.length;
      logConsumer(`Chunk: ${chunk}. Read ${bytes} characters.`);
    }
  } catch (e) {
    if (e instanceof TypeError) {
      console.log(e);
      logConsumer("TypeError: Browser may not support async iteration");
    } else {
      logConsumer(`Error in async iterator: ${e}.`);
    }
  }
}
```

 The example log below shows the code running or reports that your browser does not support async iteration of `ReadableStream`.
 The right hand side shows the received chunks; you can press the cancel button to stop the fetch.

**Note:** This fetch operation is *mocked* for the purpose of demonstration, and just returns a `ReadableStream` that generates random chunks of text.
 The "Underlying source" on the left below is the data being generated in the mocked source, while the column on the right is log from the consumer.
 (The code for the mocked source is not displayed as it is not relevant to the example.)

Creating your own custom readable stream
----------------------------------------

The Simple stream pump example we've been studying throughout this article includes a second part — once we've read the image from the fetch body in chunks, we then enqueue them into another, custom stream of our own creation. How do we create this? The `ReadableStream()` constructor.

### The ReadableStream() constructor

It is easy to read from a stream when the browser provides it for you as in the case of Fetch, but sometimes you need to create a custom stream and populate it with your own chunks. The `ReadableStream()` constructor allows you to do this via a syntax that looks complex at first, but actually isn't too bad.

The generic syntax skeleton looks like this:

```js
const stream = new ReadableStream(
  {
    start(controller) {},
    pull(controller) {},
    cancel() {},
    type,
    autoAllocateChunkSize,
  },
  {
    highWaterMark: 3,
    size: () => 1,
  },
);
```

The constructor takes two objects as parameters. The first object is required, and creates a model in JavaScript of the underlying source the data is being read from. The second object is optional, and allows you to specify a custom queuing strategy to use for your stream. You'll rarely have to do this, so we'll just concentrate on the first one for now.

The first object can contain up to five members, only the first of which is required:

1. `start(controller)` — A method that is called once, immediately after the `ReadableStream` is constructed. Inside this method, you should include code that sets up the stream functionality, e.g. beginning generation of data or otherwise getting access to the source.
2. `pull(controller)` — A method that, when included, is called repeatedly until the stream's internal queue is full. This can be used to control the stream as more chunks are enqueued.
3. `cancel()` — A method that, when included, will be called if the app signals that the stream is to be cancelled (e.g. if `ReadableStream.cancel()` is called). The contents should do whatever is necessary to release access to the stream source.
4. `type` and `autoAllocateChunkSize` — These are used — when included — to signify that the stream is to be a bytestream.
 Bytestreams are covered separately in Using readable byte streams, as they are somewhat different in purpose and use case to regular (default) streams.

Looking at our simple example code again, you can see that our `ReadableStream()` constructor only includes a single method — `start()`, which serves to read all the data out of our fetch stream.

```js
// Fetch the original image
fetch("./tortoise.png")
  // Retrieve its body as ReadableStream
  .then((response) => {
    const reader = response.body.getReader();
    return new ReadableStream({
      start(controller) {
        return pump();
        function pump() {
          return reader.read().then(({ done, value }) => {
            // When no more data needs to be consumed, close the stream
            if (done) {
              controller.close();
              return;
            }
            // Enqueue the next data chunk into our target stream
            controller.enqueue(value);
            return pump();
          });
        }
      },
    });
  });
```

### ReadableStream controllers

You'll notice that the `start()` and `pull()` methods passed into the `ReadableStream()` constructor are given `controller` parameters — these are instances of the `ReadableStreamDefaultController` class, which can be used to control your stream.

In our example we are using the controller's `enqueue()` method to enqueue a value into the custom stream after it is read from the fetch body.

In addition, when we are done reading the fetch body we use the controller's `close()` method to close the custom stream — any previously-enqueued chunks can still be read from it, but no more can be enqueued, and the stream is closed when reading has finished.

### Reading from custom streams

In our Simple stream pump example, we consume the custom readable stream by passing it into a `Response` constructor call, after which we consume it as a `blob()`.

```js
readableStream
  .then((stream) => new Response(stream))
  .then((response) => response.blob())
  .then((blob) => URL.createObjectURL(blob))
  .then((url) => console.log((image.src = url)))
  .catch((err) => console.error(err));
```

But a custom stream is still a `ReadableStream` instance, meaning you can attach a reader to it. As an example, have a look at our Simple random stream demo (see it live also), which creates a custom stream, enqueues some random strings into it, and then reads the data out of the stream again once the *Stop string generation* button is pressed.

**Note:** In order to consume a stream using `FetchEvent.respondWith()`, the enqueued stream contents must be of type `Uint8Array`; for example, encoded using `TextEncoder`.

The custom stream constructor has a `start()` method that uses a `setInterval()` call to generate a random string every second. `ReadableStreamDefaultController.enqueue()` is then used to enqueue it into the stream. When the button is pressed, the interval is cancelled, and a function called `readStream()` is invoked to read the data back out of the stream again. We also close the stream, as we've stopped enqueuing chunks to it.

```js
let interval;
const stream = new ReadableStream({
  start(controller) {
    interval = setInterval(() => {
      const string = randomChars();
      // Add the string to the stream
      controller.enqueue(string);
      // show it on the screen
      const listItem = document.createElement("li");
      listItem.textContent = string;
      list1.appendChild(listItem);
    }, 1000);
    button.addEventListener("click", () => {
      clearInterval(interval);
      readStream();
      controller.close();
    });
  },
  pull(controller) {
    // We don't really need a pull in this example
  },
  cancel() {
    // This is called if the reader cancels,
    // so we should stop generating strings
    clearInterval(interval);
  },
});
```

In the `readStream()` function itself, we lock a reader to the stream using `ReadableStream.getReader()`, then follow the same kind of pattern we saw earlier — reading each chunk with `read()`, checking whether `done` is `true` and then ending the process if so, and reading the next chunk and processing it if not, before running the `read()` method again.

```js
function readStream() {
  const reader = stream.getReader();
  let charsReceived = 0;
  let result = "";

  // read() returns a promise that resolves
  // when a value has been received
  reader.read().then(function processText({ done, value }) {
    // Result objects contain two properties:
    // done - true if the stream has already given you all its data.
    // value - some data. Always undefined when done is true.
    if (done) {
      console.log("Stream complete");
      para.textContent = result;
      return;
    }

    charsReceived += value.length;
    const chunk = value;
    const listItem = document.createElement("li");
    listItem.textContent = `Read ${charsReceived} characters so far. Current chunk = ${chunk}`;
    list2.appendChild(listItem);

    result += chunk;

    // Read some more, and call this function again
    return reader.read().then(processText);
  });
}
```

### Closing and cancelling streams

We've already shown examples of using `ReadableStreamDefaultController.close()` to close a reader. As we said before, any previously enqueued chunks will still be read, but no more can be enqueued because it is closed.

If you wanted to completely get rid of the stream and discard any enqueued chunks, you'd use `ReadableStream.cancel()` or `ReadableStreamDefaultReader.cancel()`.

Teeing a stream
---------------

Sometimes you might want to read a stream twice, simultaneously. This is achieved via the `ReadableStream.tee()` method — it outputs an array containing two identical copies of the original readable stream, which can then be read independently by two separate readers.

You might do this for example in a ServiceWorker if you want to fetch a response from the server and stream it to the browser, but also stream it to the Service Worker cache. Since a response body cannot be consumed more than once, and a stream can't be read by more than one reader at once, you'd need two copies to do this.

We provide an example of this in our Simple tee example (see it live also). This example works much the same way as our Simple random stream, except that when the button is pressed to stop generating random strings, the custom stream is taken and teed, and both resulting streams are then read:

```js
function teeStream() {
  const teedOff = stream.tee();
  readStream(teedOff[0], list2);
  readStream(teedOff[1], list3);
}
```

Pipe chains
-----------

Another feature of streams is the ability to pipe streams into one another (called a pipe chain). This involves two methods — `ReadableStream.pipeThrough()`, which pipes a readable stream through a writer/reader pair to transform one data format into another, and `ReadableStream.pipeTo()`, which pipes a readable stream to a writer acting as an end point for the pipe chain.

We do have a simple example called Unpack Chunks of a PNG (see it live also) that fetches an image as a stream, then pipes it through to a custom PNG transform stream that retrieves PNG chunks out of a binary data stream.

```js
// Fetch the original image
fetch("png-logo.png")
  // Retrieve its body as ReadableStream
  .then((response) => response.body)
  // Create a gray-scaled PNG stream out of the original
  .then((rs) => logReadableStream("Fetch Response Stream", rs))
  .then((body) => body.pipeThrough(new PNGTransformStream()))
  .then((rs) => logReadableStream("PNG Chunk Stream", rs));
```

We don't yet have an example that uses `TransformStream`.

Summary
-------

That explains the basics of "default" readable streams.

See Using readable byte streams for information about how to use readable *byte* streams: streams with an underlying byte source that can perform efficient zero-copy transfers to a consumer, bypassing the stream's internal queues.


Streams API concepts - Web APIs

Streams API concepts
====================

The Streams API adds a very useful set of tools to the web platform, providing objects allowing JavaScript to programmatically access streams of data received over the network and process them as desired by the developer. Some of the concepts and terminology associated with streams might be new to you — this article explains all you need to know.

Readable streams
----------------

A readable stream is a data source represented in JavaScript by a `ReadableStream` object that flows from an **underlying source** — this is a resource somewhere on the network or elsewhere on your domain that you want to get data from.

There are two types of underlying source:

* **Push sources** constantly push data at you when you've accessed them, and it is up to you to start, pause, or cancel access to the stream. Examples include video streams and TCP/Web sockets.
* **Pull sources** require you to explicitly request data from them once connected to. Examples include a file access operation via a `fetch()` request.

The data is read sequentially in small pieces called **chunks**. A chunk can be a single byte, or it can be something larger such as a typed array of a certain size. A single stream can contain chunks of different sizes and types.

![Readable streams data flow](/en-US/docs/Web/API/Streams_API/Concepts/readable_streams.png)

The chunks placed in a stream are said to be **enqueued** — this means they are waiting in a queue ready to be read. An **internal queue** keeps track of the chunks that have not yet been read (see the Internal queues and queuing strategies section below).

The chunks inside the stream are read by a **reader** — this processes the data one chunk at a time, allowing you to do whatever kind of operation you want to do on it. The reader plus the other processing code that goes along with it is called a **consumer**.

There is also a construct you'll use called a **controller** — each reader has an associated controller that allows you to control the stream (for example, to close it if wished).

Only one reader can read a stream at a time; when a reader is created and starts reading a stream (an **active reader**), we say it is **locked** to it. If you want another reader to start reading your stream, you typically need to cancel the first reader before you do anything else (although you can **tee** streams, see the Teeing section below)

Note that there are two different types of readable stream. As well as the conventional readable stream there is a type called a byte stream — this is an extended version of a conventional stream for reading underlying byte sources. Compared with the conventional readable stream, byte streams are allowed to be read by BYOB readers (BYOB, "bring your own buffer"). This kind of reader allows streams to be read straight into a buffer supplied by the developer, minimizing the copying required. Which underlying stream (and by extension, reader and controller) your code will use depends on how the stream was created in the first place (see the `ReadableStream()` constructor page).

You can make use of ready-made readable streams via mechanisms like a `Response.body` from a fetch request, or roll your own streams using the `ReadableStream()` constructor.

Teeing
------

Even though only a single reader can read a stream at once, it is possible to split a stream into two identical copies, which can then be read by two separate readers. This is called **teeing**.

In JavaScript, this is achieved via the `ReadableStream.tee()` method — it outputs an array containing two identical copies of the original readable stream, which can then be read independently by two separate readers.

You might do this for example in a ServiceWorker if you want to fetch a response from the server and stream it to the browser, but also stream it to the ServiceWorker cache. Since a response body cannot be consumed more than once, and a stream can't be read by more than one reader at once, you'd need two copies to do this.

![Teeing data flow](/en-US/docs/Web/API/Streams_API/Concepts/tee.png)

Writable streams
----------------

A **writable stream** is a destination into which you can write data, represented in JavaScript by a `WritableStream` object. This serves as an abstraction over the top of an **underlying sink** — a lower-level I/O sink into which raw data is written.

The data is written to the stream via a **writer**, one chunk at a time. A chunk can take a multitude of forms, just like the chunks in a reader. You can use whatever code you like to produce the chunks ready for writing; the writer plus the associated code is called a **producer**.

When a writer is created and starts writing to a stream (an **active writer**), it is said to be **locked** to it. Only one writer can write to a writable stream at one time. If you want another writer to start writing to your stream, you typically need to abort it before you then attach another writer to it.

An **internal queue** keeps track of the chunks that have been written to the stream but not yet been processed by the underlying sink.

There is also a construct you'll use called a controller — each writer has an associated controller that allows you to control the stream (for example, to abort it if wished).

![Writable streams data flow](/en-US/docs/Web/API/Streams_API/Concepts/writable_streams.png)

You can make use of writable streams using the `WritableStream()` constructor. These currently have very limited availability in browsers.

Pipe chains
-----------

 The Streams API makes it possible to pipe streams into one another using a structure called a **pipe chain**.
 There are two methods that facilitate this:

* `ReadableStream.pipeThrough()` — pipes the stream through a **transform stream**, potentially transforming the data format along the way.
 This might be used, for example, to encode or decode video frames, compress or decompress data, or otherwise convert data from one form to another.
 A transform stream consists of a pair of streams: a readable stream from which data is read and a writable stream into which it is written, along with appropriate mechanisms to ensure that new data is made available to read as soon as data is written.
 `TransformStream` is a concrete implementation of a transform stream, but any object that has the same readable stream and writable stream properties can be passed to `pipeThrough()`.
* `ReadableStream.pipeTo()` — pipes to a writable stream that acts as the end point of the pipe chain.

The start of the pipe chain is called the **original source**, and the end is called the **ultimate sink**.

![pipe chain diagram](/en-US/docs/Web/API/Streams_API/Concepts/pipechain.png)

Backpressure
------------

An important concept in streams is **backpressure** — this is the process by which a single stream or a pipe chain regulates the speed of reading/writing. When a stream later in the chain is still busy and isn't yet ready to accept more chunks, it sends a signal backwards through the chain to tell earlier transform streams (or the original source) to slow down delivery so that you don't end up with a bottleneck anywhere.

To use backpressure in a `ReadableStream`, we can ask the controller for the chunk size desired by the consumer by querying the `ReadableStreamDefaultController.desiredSize` property on the controller. If it is too low, our `ReadableStream` can tell its underlying source to stop sending data, and we backpressure along the stream chain.

If later on the consumer again wants to receive data, we can use the pull method in the stream creation to tell our underlying source to feed our stream with data.

Internal queues and queuing strategies
--------------------------------------

As mentioned earlier, the chunks in a stream that have not yet been processed and finished with are kept track of by an internal queue.

* In the case of readable streams, these are the chunks that have been enqueued but not yet read
* In the case of writable streams, these are chunks that have been written but not yet processed by the underlying sink.

Internal queues employ a **queuing strategy**, which dictates how to signal backpressure based on the **internal queue state.**

In general, the strategy compares the size of the chunks in the queue to a value called the **high water mark**, which is the largest total chunk size that the queue would prefer to manage.

The calculation performed is

`high water mark - total size of chunks in queue = desired size`

 The **desired size** is the number of chunks the stream can still accept to keep the stream flowing but below the high water mark in size.
 Chunk generation will be slowed down/sped up as appropriate to keep the stream flowing as fast as possible while keeping the desired size above zero.
 If the value falls to zero (or below), it means that chunks are being generated faster than the stream can cope with, which may result in problems.

 As an example, let's take a chunk size of 1, and a high water mark of 3.
 This means that up to 3 chunks can be enqueued before the high water mark is reached and backpressure is applied.


CSSMathValue: operator property - Web APIs

CSSMathValue: operator property
===============================

 The **`CSSMathValue.operator`** read-only
 property of the `CSSMathValue` interface indicates the operator that the
 current subtype represents. For example, if the current `CSSMathValue`
 subtype is `CSSMathSum`, this property will return the string
 `"sum"`.

Value
-----

A `String`.

| Interface | Value |
| --- | --- |
| `CSSMathSum` | `"sum"` |
| `CSSMathProduct` | `"product"` |
| `CSSMathMin` | `"min"` |
| `CSSMathMax` | `"max"` |
| `CSSMathClamp` | `"clamp"` |
| `CSSMathNegate` | `"negate"` |
| `CSSMathInvert` | `"invert"` |

Examples
--------

 We create an element with a `width`
 determined using a `calc()` function,
 then `console.log()` the
 `operator`.

```html
<div>My width has a <code>calc()</code> function</div>
```

We assign a `width` with a calculation

```css
div {
  width: calc(50% - 0.5vw);
}
```

We add the JavaScript

```js
const styleMap = document.querySelector("div").computedStyleMap();

console.log(styleMap.get("width")); // CSSMathSum {values: CSSNumericArray, operator: "sum"}
console.log(styleMap.get("width").values); // CSSNumericArray {0: CSSUnitValue, 1: CSSMathNegate, length: 2}
console.log(styleMap.get("width").operator); // 'sum'
console.log(styleMap.get("width").values[1].operator); // 'negate'
```

 The `CSSMathValue.operator` returns `sum` for the equation and
 `negate` for the operator on the second value.

Specifications
--------------

| Specification |
| --- |
| CSS Typed OM Level 1 # dom-cssmathvalue-operator |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


MediaKeySession: close() method - Web APIs

MediaKeySession: close() method
===============================

 The `MediaKeySession.close()` method notifies that the current media session
 is no longer needed, and that the content decryption module should release any resources
 associated with this object and close it. Then, it returns a `Promise`.

Syntax
------

```js
close()
```

### Parameters

None.

### Return value

A `Promise`.

Specifications
--------------

| Specification |
| --- |
| Encrypted Media Extensions # dom-mediakeysession-close |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


MediaKeySession: keyStatuses property - Web APIs

MediaKeySession: keyStatuses property
=====================================

 The **`MediaKeySession.keyStatuses`** read-only property
 returns a reference to a read-only `MediaKeyStatusMap` of the current
 session's keys and their statuses.

Value
-----

A `MediaKeyStatusMap`.

Specifications
--------------

| Specification |
| --- |
| Encrypted Media Extensions # dom-mediakeysession-keystatuses |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


MediaKeySession: generateRequest() method - Web APIs

MediaKeySession: generateRequest() method
=========================================

 The `MediaKeySession.generateRequest()` method returns a
 `Promise` after generating a media request based on initialization data.

Syntax
------

```js
generateRequest()
```

### Parameters

None.

### Return value

A `Promise`.

Specifications
--------------

| Specification |
| --- |
| Encrypted Media Extensions # dom-mediakeysession-generaterequest |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


MediaKeySession: closed property - Web APIs

MediaKeySession: closed property
================================

 The `MediaKeySession.closed` read-only property returns a
 `Promise` signaling when a `MediaKeySession` closes. This
 promise can only be fulfilled and is never rejected. Closing a session means that
 licenses and keys associated with it are no longer valid for decrypting media data.

Value
-----

A `Promise`.

Specifications
--------------

| Specification |
| --- |
| Encrypted Media Extensions # dom-mediakeysession-closed |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


MediaKeySession: load() method - Web APIs

MediaKeySession: load() method
==============================

 The `MediaKeySession.load()` method returns a `Promise` that
 resolves to a boolean value after loading data for a specified session object.

Syntax
------

```js
load(sessionId)
```

### Parameters

`sessionId`

 A unique string generated by the content description module for the current media
 object and its associated keys or licenses.

### Return value

 A `Promise` that resolves to a boolean indicating whether the load
 succeeded or failed.

Specifications
--------------

| Specification |
| --- |
| Encrypted Media Extensions # dom-mediakeysession-load |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


MediaKeySession: sessionId property - Web APIs

MediaKeySession: sessionId property
===================================

 The **`MediaKeySession.sessionId`** is a read-only property
 that contains a unique string generated by the content decryption module (CDM) for the
 current media object and its associated keys or licenses.

Value
-----

A string.

Specifications
--------------

| Specification |
| --- |
| Encrypted Media Extensions # dom-mediakeysession-sessionid |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


MediaKeySession: update() method - Web APIs

MediaKeySession: update() method
================================

 The `MediaKeySession.update()` method loads messages and licenses to the
 CDM, and then returns a `Promise` .

Syntax
------

```js
update(response)
```

### Parameters

`response`

An instance that is either an `ArrayBuffer`, a `TypedArray`, or a `DataView`.

### Return value

A `Promise`.

Specifications
--------------

| Specification |
| --- |
| Encrypted Media Extensions # dom-mediakeysession-update |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


MediaKeySession: remove() method - Web APIs

MediaKeySession: remove() method
================================

The `MediaKeySession.remove()` method returns a `Promise` after removing any session data associated with the current object.

Syntax
------

```js
remove()
```

### Parameters

None.

### Return value

None (`undefined`).

Return value
------------

A `Promise` that resolves to a boolean indicating whether the load succeeded or failed.

Specifications
--------------

| Specification |
| --- |
| Encrypted Media Extensions # dom-mediakeysession-remove |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


MediaKeySession: message event - Web APIs

MediaKeySession: message event
==============================

 The **`message`** event of the
 `MediaKeySession` interface fires when a message is generated by the
 content decryption module.

Syntax
------

Use the event name in methods like `addEventListener()`, or set an event handler property.

```js
addEventListener("message", (event) => {});

onmessage = (event) => {};
```

Event type
----------

A `MediaKeyMessageEvent`. Inherits from `Event`.

Event

MediaKeyMessageEventEvent properties
----------------

`MediaKeyMessageEvent.message` Read only

Returns an `ArrayBuffer` with a message from the content decryption module. Messages vary by key system.

`MediaKeyMessageEvent.messageType` Read only

Indicates the type of message. May be one of `license-request`, `license-renewal`, `license-release`, or `individualization-request`.

Specifications
--------------

| Specification |
| --- |
| Encrypted Media Extensions # dom-mediakeysession-onmessage |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


MediaKeySession: keystatuseschange event - Web APIs

MediaKeySession: keystatuseschange event
========================================

The **`keystatuseschange`** event of the `MediaKeySession` API fires when there has been a change in the keys or their statuses within a session.

Syntax
------

Use the event name in methods like `addEventListener()`, or set an event handler property.

```js
addEventListener("keystatuseschange", (event) => {});

onkeystatuseschange = (event) => {};
```

Event type
----------

An `ExtendableEvent`. Inherits from `Event`.

Event

ExtendableEventEvent properties
----------------

*Doesn't implement any specific properties, but inherits properties from its parent, `Event`.*

Specifications
--------------

| Specification |
| --- |
| Encrypted Media Extensions # dom-mediakeysession-onkeystatuseschange |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


MediaKeySession: expiration property - Web APIs

MediaKeySession: expiration property
====================================

 The **`MediaKeySession.expiration`** read-only property returns
 the time after which the keys in the current session can no longer be used to decrypt
 media data, or NaN if no such time exists. This value is determined by the CDM and
 measured in milliseconds since January 1, 1970, UTC. This value may change during a
 session lifetime, such as when an action triggers the start of a window.

Value
-----

A number or NaN.

Specifications
--------------

| Specification |
| --- |
| Encrypted Media Extensions # dom-mediakeysession-expiration |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.