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.
Asked
Active
Viewed 8,660 times
12
-
1Not true. The promise from a fetch() resolves after the first packet has been received, but doesn't wait until the whole body is there. – Touffy Nov 16 '17 at 20:55
-
1https://stackoverflow.com/questions/36453950/upload-file-with-fetch-api-in-javascript-and-show-progress – Adrian Nov 16 '17 at 20:58
-
1then https://stackoverflow.com/questions/35711724/progress-indicators-for-fetch would be better besides it's older – skyboyer Nov 16 '17 at 20:59
-
Can't flag as duplicate because of the bounty, but it's all there. – Touffy Nov 16 '17 at 21:02
-
Adriani6 Touffy thanks a lot for that information – skyboyer Nov 16 '17 at 21:15
-
4Let's reopen because this question is download specific, and the suggested duplicate answer is upload-specific – AnthumChris May 01 '20 at 01:25
2 Answers
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"> </div>
<h1 id="progress"> </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