Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Read a stream with for-await using JavaScript in the browser

Tags:

javascript

I am using the following code to download something large upon user gesture in the browser using fetch with progress indication:

const url = 'https://source.unsplash.com/random';
const response = await fetch(url);
const total = Number(response.headers.get('content-length'));
let loaded = 0;
const reader = response.body.getReader();
let result;
while (!(result = await reader.read()).done) {
  loaded += result.value.length;
  // Display loaded/total in the UI
}

I saw a snippet in a related question which lead me to believe this could be simplified into:

const url = 'https://source.unsplash.com/random';
const response = await fetch(url);
const total = Number(response.headers.get('content-length'));
let loaded = 0;
for await (const result of response.body.getReader()) {
  loaded += result.value.length;
  // Display loaded/total in the UI
}

getReader returns a ReadableStreamDefaultReader which comes from the Streams API which is a web API as well as a Node API which makes finding only web related information really hard.

In the snippet above the code fails with response.body.getReader(...) is not a function or its return value is not async iterable. I checked the object prototype and indeed I don't think it has Symbol.asyncIterator on it so yeah no wonder the browser failed to iterate over it.

So the code in that question must have been wrong, but now I wonder, is there a way to take a stream like this and iterate over it using for-await? I suppose you need wrap it in an async generator and yield the chunks in a similar way to the first snippet, right?

Is there an existing or planned API which makes this more streamlined, something closer to the second snippet, but actually working?

like image 320
Tomáš Hübelbauer Avatar asked Feb 17 '26 04:02

Tomáš Hübelbauer


2 Answers

The ReadableStream itself implements an async iterable

 for await (const result of response.body) {
   loaded += result.value.length;
   // Display loaded/total in the UI
 }
like image 52
Jonas Wilms Avatar answered Feb 18 '26 16:02

Jonas Wilms


The response.body.getReader() .read() returns {value:..., done: Boolean} meets exactly what asyncIterator needs; before the ReadableStream itself got asyncIterator implementation, you can easily polyfill it:

if (!response.body[Symbol.asyncIterator]) {
  response.body[Symbol.asyncIterator] = () => {
    const reader = response.body.getReader();
    return {
      next: () => reader.read(),
    };
  };
}
for await (const result of response.body) {
  loaded += result.length;
  console.log(((loaded / total) * 100).toFixed(2), '%');
}

See https://jsfiddle.net/6ostwkr2/

like image 35
TomasJ Avatar answered Feb 18 '26 16:02

TomasJ



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!