Search code examples
file-uploadfetch

Polyfill for upload progress of fetch, without stream


I ran into the problem of Fetch API does not support progress. I've read through the related topics and ReadableStreams looks like a good candidate, however we cannot use it due to our dependency policy (still an early stage and has to be polyfilled in many browsers).

So we needed a polyfill built on recently available APIs, most likely XHR. I though I would share our implementation. It is not very complex though, but might save some time for others.


Solution

  • 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(_ => /*...*/);