question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

The stream is not in a state that permits enqueue; new Response(readable).blob().text() resolve to [object ReadableStream] not underlying source

See original GitHub issue

Consider this code:

Promise.allSettled([
  new Promise(async (resolve, reject) => {
    let controller;
    const rs = new ReadableStream(
      {
        start(_) {
          return (controller = _);
        },
      },
      { highWaterMark: 1 }
    );
    for (let chunk of Array.from({ length: 100 }, (_, i) => i)) {
      try {
        controller.enqueue(new Uint8Array([chunk]));
      } catch (err) {
        console.warn(
          `new Response().arrayBuffer() outside event handler: ${err.message}`
        );
        reject(err);
      }
    }
    controller.close();
    const buffer = await new Response(rs).arrayBuffer();
    resolve(buffer);
  }),
  new Promise(async (resolve, reject) => {
    let controller;
    const rs = new ReadableStream(
      {
        start(_) {
          return (controller = _);
        },
      },
      { highWaterMark: 1 }
    );
    const ac = new AudioContext();
    if (ac.state === 'suspended') {
      await ac.resume();
    }
    const msd = new MediaStreamAudioDestinationNode(ac);
    const osc = new OscillatorNode(ac);
    osc.connect(msd);
    osc.onended = () => {
      recorder.requestData();
      recorder.stop();
    };

    const recorder = new MediaRecorder(msd.stream, {
      audioBitrateMode: 'constant',
    });
    recorder.onstop = async (e) => {
      // console.log(e.type);
      const buffer = await new Response(rs).arrayBuffer();
      resolve(buffer);
    };
    recorder.ondataavailable = async (e) => {
      // console.log(e.type, e.data.size);
      if (e.data.size > 0) {
        try {
          controller.enqueue(new Uint8Array(await e.data.arrayBuffer()));
        } catch (err) {
          console.warn(`Response.arrayBuffer(): ${err.message}`);
          reject(err);
        }
      } else {
        controller.close();
        await ac.close();
      }
    };
    osc.start(ac.currentTime);
    recorder.start(1);
    osc.stop(ac.currentTime + 1.15);
  }),
  new Promise(async (resolve, reject) => {
    let controller;
    const rs = new ReadableStream(
      {
        start(_) {
          return (controller = _);
        },
      },
      { highWaterMark: 1 }
    );
    const ac = new AudioContext();
    if (ac.state === 'suspended') {
      await ac.resume();
    }
    const msd = new MediaStreamAudioDestinationNode(ac);
    const osc = new OscillatorNode(ac);
    osc.connect(msd);
    osc.onended = () => {
      recorder.requestData();
      recorder.stop();
    };

    const recorder = new MediaRecorder(msd.stream, {
      audioBitrateMode: 'constant',
    });
    recorder.onstop = async (e) => {
      // console.log(e.type);
      const buffer = await new Response(rs).blob();
      resolve(buffer);
    };
    recorder.ondataavailable = async (e) => {
      // console.log(e.type, e.data.size);
      if (e.data.size > 0) {
        try {
          controller.enqueue(new Uint8Array(await e.data.arrayBuffer()));
        } catch (err) {
          console.warn(`Response.blob(): ${err.message}`);
          reject(err);
        }
      } else {
        controller.close();
        await ac.close();
      }
    };
    osc.start(ac.currentTime);
    recorder.start(1);
    osc.stop(ac.currentTime + 1.15);
  }),
])
  .then(async (data) => {
    console.table(data);
    console.log(await data[2].value.text());
  })
  .catch((err) => console.error(err));

Using Streams API shipped with Chromium 96.0.4651.0 (Developer Build) (64-bit) Revision aabda36a688c0883019e7762faa00db6342a7e37-refs/heads/main@{#924134} this error is thrown

TypeError: Failed to execute 'enqueue' on 'ReadableStreamDefaultController': Cannot enqueue a chunk into a readable stream that is closed or has been requested to be closed

This polyfill, included as

  <script src="https://unpkg.com/web-streams-polyfill/dist/polyfill.min.js"></script>

at https://plnkr.co/edit/XwtpbXt4aqTQTIhb?preview throws

The stream is not in a state that permits enqueue

Observe that the first element of the array passed to Promise.allSettled() uses a basic loop in which ReadableStreamDefaultController.enqueue() is called with a Uint8Array() as value, then close() is called, then the ReadableStream is passed to new Response() and read to completion using blob().

The second element of the array follows the same pattern as the first element, except close() being called in MediaRecorder dataavailable event handler, and new Response(readable).arrayBuffer() called in onstop event handler. The TypeError is still thrown, logged at console, yet not caught in try..catch.

The third element of the array follows same pattern as first and second element, though uses Response.blob(), which always throws the TypeError.

I do not see anywhere in the code where enqueue() is called after close().

Using the polyfill I did observe where the third element fulfilled, was not rejected, while the very next test with the same code blob() throws an error.

The Streams API shipped with Chromium always throws https://bugs.chromium.org/p/chromium/issues/detail?id=1253143.

The polyfill does not resolve to the underlying data enqueued when Response(readable).blob(), rather appears to resolve to a string

[object ReadableStream]

There should not be any error thrown. The sequence is close(), then new Response(readable) with arrayBuffer() or blob() chained.

This behaviour of both Chromium Streams API and this polyfill makes no sense to me. (This cannot be working as intended.) Kindly illuminate.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:10 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
guest271314commented, Sep 26, 2021

Thank you, again for your analysis and solution. I will re-read the code until I gather exactly what is occuring, though I am now reminded of promises.push(new Promise(resolve => {})) in synchronous for loop then Promise.all(promises).

1reaction
MattiasBuelenscommented, Sep 26, 2021
  1. await e.data.arrayBuffer() from the previous event resolves, and you try to enqueue a chunk after the stream is closed.

Where does that occur? No data is enqueued when Blob size is 0.

Not that one, the one with blob.size = 733. The first ondataavailable call isn’t “done” yet after step 2, it lasts all the way until step 5. Meanwhile, another ondataavailable event is fired in step 3 with blob.size = 0. That’s what’s causing the seemingly “out of order” execution.

The key insight is to realize that the event loop does not await the result of your ondataavailable handler, so it’ll happily call it again as soon as the synchronous call is done.

Ah, that’s an unfortunate limitation of (the current version of) the polyfill.

That should probably be included in https://github.com/MattiasBuelens/web-streams-polyfill#compliance list items?

It’s still technically true though: the polyfill faithfully implements the entire Streams API. What it doesn’t do is implement all other web APIs that integrate with the Streams API, such as fetch.

I do see this sort of question coming up more frequently, so I’ll try to explain this better in the README.

Yes. I was previously just pushing Blobs to an array then calling Blob(chunks).

I mean, that’s probably even easier? You can pass other Blobs to the Blob constructor, and then call concatenatedBlob.arrayBuffer() at the end. If you’re not interested in the concrete byte data of any individual chunk, you don’t really need to turn each chunk into its own Uint8Array first.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Using readable streams - Web APIs | MDN
This provides us with a ReadableStream object. Attaching a reader. Now we've got our streaming body, reading the stream requires attaching a ...
Read more >
Streams—The definitive guide - web.dev
The ReadableStream() constructor creates and returns a readable stream object from the given handlers. There are two types of underlying source:.
Read more >
Implementing the Web Streams API in Node.js - James M Snell
When the ReadableStream is created, the underlying source's start() method is called, which, in this case, starts an async iterator using Node.
Read more >
Using readable streams
In our Simple stream pump example, we consume the custom readable stream by passing it into a Response constructor call, after which we...
Read more >
Stream | Node.js v18 API
Data is buffered in Readable streams when the implementation calls stream.push(chunk) . If the consumer of the Stream does not call stream.read() ,...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found