A quick tip: in a previous demo, I showed how we can download a large file to seed the content for a Service Worker. If you look fast enough, you'll see a progress indicator. (Although for a small file, blink and you'll miss it!) 👀
The code is pretty simple. Let's start with a simple async fetch
:
async function downloadFile(url) {
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
const bytes = new Uint8Array(arrayBuffer);
// do something with bytes
}
The arrayBuffer
call waits until the entire target has downloaded before returning the bytes. Instead, we can consume 'chunks' of the file (since we'll get parts of the file over time) at a time, to get a sense of percentage.
Check The Header
Firstly, we read the "Content-Length" header of our response: this is something the server sends us before the data, so we can actually work out how far along we've gone:
const response = await fetch(url);
const length = response.headers.get('Content-Length');
if (!length) {
// something was wrong with response, just give up
return await response.arrayBuffer();
}
If there's no valid header, then either there's something wrong with the response, or the server hasn't told us how long it is. You can just fall back to whatever you were doing before.
Chunks
Your browser is receiving chunks of bytes from the remote server as the data arrives. Since we know how long the total response will be, we can prepare a buffer for it:
const array = new Uint8Array(length);
let at = 0; // to index into the array
And grab the reader, which lets us get chunks:
const reader = response.body.getReader();
Now, we can store where we're up to (in at
), and insert every new chunk into the output:
for (;;) {
const {done, value} = await reader.read();
if (done) {
break;
}
array.set(value, at);
at += value.length;
}
return array;
Within the loop above, we can log the progress as a percentage, something like:
progress.textContent = `${(at / length).toFixed(2)}%`;
Then as above, just return the array: we're done.
Fin
20 👋
Top comments (8)
Nice! I've been wondering lately about checking the progress of a fetch request. However, you use a
for
loop without any arguments in your example:Why not use a
while
loop?It's honestly just preference! If you want to be pedantic, then my way is less work: the
while (...)
loop checksdownloadIsDone
even though we know it's false already, but this is splitting hairs.FWIW, I write a lot of Go, which has a "naked" for loop:
for { ... }
, which I quite like—it's just the same as using(;;)
in the body of a JS for loop.Doesnt seem to be working with node-fetch
let response = await fetch('api.github.com/repos/javascripttut...);
I don't believe node-fetch supports this. Read more.
This does not work well when
content-encoding
is in effect.content-length
does not match then the length of the contents streamed.This can be solved with a server-side assist. Here's a performant
x-file-size
header example with Nginx:github.com/AnthumChris/fetch-progr...
I assume the
at
variable is something like?..Oh yeah, I'll fix that. Thanks!