Upload Progress Indicators For Fetch

Upload progress indicators for fetch?

Streams are starting to land in the web platform (https://jakearchibald.com/2016/streams-ftw/) but it's still early days.

Soon you'll be able to provide a stream as the body of a request, but the open question is whether the consumption of that stream relates to bytes uploaded.

Particular redirects can result in data being retransmitted to the new location, but streams cannot "restart". We can fix this by turning the body into a callback which can be called multiple times, but we need to be sure that exposing the number of redirects isn't a security leak, since it'd be the first time on the platform JS could detect that.

Some are questioning whether it even makes sense to link stream consumption to bytes uploaded.

Long story short: this isn't possible yet, but in future this will be handled either by streams, or some kind of higher-level callback passed into fetch().

Upload file with Fetch API in Javascript and show progress

This is NOT possible. The reason is the way the Fetch API works.

The fetch method returns a Promise; the Promise API uses a then method to which you can attach “success” and “failure” callbacks. Therefore, you can gain access to progress.

Still, don't lose hope! There is a workaround that can do the trick (I found it on github repository of the Fetch API):

you can convert the request to a stream request and then when a response return is just a bitarray of the file content. then you need to collect all of the data and when its end decode it to the file you want

function consume(stream, total = 0) {
while (stream.state === "readable") {
var data = stream.read()
total += data.byteLength;
console.log("received " + data.byteLength + " bytes (" + total + " bytes in total).")
}
if (stream.state === "waiting") {
stream.ready.then(() => consume(stream, total))
}
return stream.closed
}
fetch("/music/pk/altes-kamuffel.flac")
.then(res => consume(res.body))
.then(() => console.log("consumed the entire body without keeping the whole thing in memory!"))
.catch((e) => console.error("something went wrong", e))

How to get progress from Fetch API?

As of right now, you can't. It's not part of the spec yet. There was an issue for it on the spec's repo but it's closed. There's also some discussion on this issue about it.

Long story short, you are probably better off just sticking with xhr for now unless you want to try some of the polyfills/workarounds in the second issue Iinked.

It's just not supported, and that is also the reason fetch requests don't show up in the network tab in the devtools untill they've completed.

Polyfill for upload progress of fetch, without stream

Here's our solution which falls back to XHR, while trying to stay close to fetch's signature. Note, that this focuses on the progress, response status and response data are omitted.

interface OnProgress {
(loaded: number, total: number, done: boolean): void;
}

export function fetch(url: string, { method, headers, body }, onProgress: OnProgress): Promise {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.upload.addEventListener('progress', e => onProgress(e.loaded, e.total, false));
xhr.upload.addEventListener('loadend', _ => onProgress(undefined, undefined, true));
xhr.onreadystatechange = () => {
if (xhr.readyState === 4)
resolve();
};
xhr.onerror = err => reject(err);
Object.entries(headers).forEach(([name, value]) => xhr.setRequestHeader(name, value));
xhr.send(body);
});
}

Usage example:

import { fetch } from 'my-fetch-progress';

const formData = new FormData();
formData.append('file', file, file.name);
const onProgress = (loaded, total, done) => console.log('onProgress', loaded, total, done);

fetch('/my-upload', {
method: 'POST',
headers: { 'Authorization': `Bearer ...` },
body: formData
}, onProgress)
.then(_ => /*...*/);


Related Topics



Leave a reply



Submit