12

I am trying to capture the download progress of a Fetch request and use that to change the width of a progress bar. I looked at ProgressEvent.lengthComputable as a potential solution but unsure if this can be used with the Fetch API.

AnthumChris
  • 6,885
  • 2
  • 22
  • 47
Jai Sandhu
  • 114
  • 3
  • 18

2 Answers2

9

without checking for errors (as in try/catch etc...)

const elStatus = document.getElementById('status');
function status(text) {
  elStatus.innerHTML = text;
}

const elProgress = document.getElementById('progress');
function progress({loaded, total}) {
  elProgress.innerHTML = Math.round(loaded/total*100)+'%';
}

async function main() {
  status('downloading with fetch()...');
  const response = await fetch('https://fetch-progress.anthum.com/30kbps/images/sunrise-baseline.jpg');
  const contentLength = response.headers.get('content-length');
  const total = parseInt(contentLength, 10);
  let loaded = 0;

  const res = new Response(new ReadableStream({
    async start(controller) {
      const reader = response.body.getReader();
      for (;;) {
        const {done, value} = await reader.read();
        if (done) break;
        loaded += value.byteLength;
        progress({loaded, total})
        controller.enqueue(value);
      }
      controller.close();
    },
  }));
  const blob = await res.blob();
  status('download completed')
  document.getElementById('img').src = URL.createObjectURL(blob);
}

main();
<div id="status">&nbsp;</div>
<h1 id="progress">&nbsp;</h1>
<img id="img" />

adapted from here

gman
  • 92,182
  • 29
  • 231
  • 348
  • This doesn't work if the server sends compressed encoded response. For example gzip compressed. Say the client sends `Accept-Encoding: gzip` header, and server responds with - `Content-Type: application/json` `Content-Encoding: gzip` `Content-Length: xxx` then the length `xxx` will much smaller than the total length of chunks while reading from the body reader. Basically `loaded` will be more than `total` after a certain point. Because the `content-length` header contains the size of the compressed response. But `loaded` is the chunk size calculated after decompressing. – ecthiender Feb 18 '22 at 13:33
  • Apparently you're out of luck for compressed stuff period. There's no standard header that sends the size of the compressed data. If you control the server you could send a custom header with that info. – gman Feb 18 '22 at 17:33
-4

you can use axios instead

import axios from 'axios'
export async function uploadFile(file, cb) {
  const url = `//127.0.0.1:4000/profile`
  try {
    let formData = new FormData()
    formData.append("avatar", file)
    const data = await axios.post(url, formData, {
      onUploadProgress: (progressEvent) => {
        console.log(progressEvent)
        if (progressEvent.lengthComputable) {
          let percentComplete = progressEvent.loaded / progressEvent.total;
          if (cb) {
            cb(percentComplete)
          }
        }
      }
    })
    return data
  } catch (error) {
    console.error(error)
  }
}
Haile
  • 69
  • 1
  • 7