loading...

Progress Indicator With Fetch

samthor profile image Sam Thorogood Updated on ・2 min read

Blog-A-Day in June (19 Part Series)

1) Rebuild only when necessary in Node 2) Civilization is a game you never lose 3 ... 17 3) Arrow functions break JavaScript parsers 4) Detecting Select All on the Web 5) Declaring JS Variables in 2019 6) Sam's dotfiles highlights 7) Automate Reading Form Results with πŸ€– Chrome 8) Beyond appendChild: Better convenience methods for HTML 9) AMA, Sam 10-yr Googler in Web DevRel 10) Disable a HTML form while in-flight using fieldset 11) PWAs that download like apps πŸ—œοΈ 12) Matching elements with selectors in JS 13) Install This PWA To Continue 14) Google Assistant now supports "Open/Close" devices 15) Modern Web Components 16) What To Expect When You're Expecting To Drop IE11 πŸ—‘οΈ 17) Divert Vertical Scroll To The Side ↔️ 18) Graceful Shutdown Is A Lie 19) Progress Indicator With Fetch

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 πŸ‘‹

Blog-A-Day in June (19 Part Series)

1) Rebuild only when necessary in Node 2) Civilization is a game you never lose 3 ... 17 3) Arrow functions break JavaScript parsers 4) Detecting Select All on the Web 5) Declaring JS Variables in 2019 6) Sam's dotfiles highlights 7) Automate Reading Form Results with πŸ€– Chrome 8) Beyond appendChild: Better convenience methods for HTML 9) AMA, Sam 10-yr Googler in Web DevRel 10) Disable a HTML form while in-flight using fieldset 11) PWAs that download like apps πŸ—œοΈ 12) Matching elements with selectors in JS 13) Install This PWA To Continue 14) Google Assistant now supports "Open/Close" devices 15) Modern Web Components 16) What To Expect When You're Expecting To Drop IE11 πŸ—‘οΈ 17) Divert Vertical Scroll To The Side ↔️ 18) Graceful Shutdown Is A Lie 19) Progress Indicator With Fetch

Posted on by:

samthor profile

Sam Thorogood

@samthor

Developer Relations for Web at Google.

Discussion

markdown guide
 

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:

for (;;) { '...' }

Why not use a while loop?

let downloadIsDone = false

while (!downloadIsDone) {
  const { done, value } = await reader.read()

  downloadIsDone = done
}
 

It's honestly just preference! If you want to be pedantic, then my way is less work: the while (...) loop checks downloadIsDone 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...);

const reader = response.body.getReader();

This gives error -> Property 'getReader' does not exist on type 'ReadableStream'.
 

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

let at = 0;
 

Oh yeah, I'll fix that. Thanks!