Search code examples
javascriptfetch-apiwhatwg-streams-api

How to get File upload progress with fetch() and WhatWG streams


Note: I'm not looking for any alternatives. I know this can be done with XMLHttpRequest. I also don't care about browser support. I just want learn about the new/upcoming standards.

I have a File object and I can upload it with PUT using fetch like this:

fetch(url, {
    method: "PUT",
    body: fileObject,
});

How can I get upload progress from this?

From what I understand the body of the fetch options can be a ReadableStream. So maybe there is a way to wrap the File object to a ReadableStream and get progress status from that?

Eg. something like this

fetch(url, {
    method: "PUT",
    body: asReadableStream(fileObject, onProgress),
});

Thanks.


Solution

  • Update

    Chrome started to support streaming uploads https://chromestatus.com/features/5274139738767360

    Here is a demo using a pull stream that the request calls when it's ready to accept more data for uploading

    let uploaded = 0
    let buf = new Uint8Array(1024 * 50)
    let start = Date.now()
    
    var rs = new ReadableStream({
      pull(ctrl) {
        uploaded += buf.byteLength
        console.log('uploaded', uploaded)
        crypto.getRandomValues(buf)
        ctrl.enqueue(buf)
        if ((start + 1000) < Date.now()) ctrl.close()
      }
    })
    
    fetch('https://httpbin.org/post', {
      method: 'POST',
      body: rs,
      duplex: 'half'
    }).then(r => r.json()).then(console.log)

    As Kyle said, ReadableStream uploading is not supported yet. https://github.com/whatwg/fetch/issues/95

    Even if it is possible I would not try to monitor the upload progress throught streams, (that is if FetchObserver becomes a thing) Nobody is working on it right now. But Mozilla made a proposal that looks something like this.

    /*
    enum FetchState {
      // Pending states
      "requesting", "responding",
    
      // Final states
      "aborted", "errored", "complete"
    };
    */
    
    fetch(url, {
      observe(observer) { 
        observer.onresponseprogress = e => console.log(e);
        observer.onrequestprogress = e => console.log(e);
        observer.onstatechange = n => console.log(observer.state)
      }
    )
    

    I remember that i tested it using some experimental flags a long time ago but can't find the demo anymore, guess they removed it from MDN since it was there own implementation/suggestion.

    enqueue bytes to a readable or a identity stream don't mean that you have uploaded the data to the server, it only signals that the request asking for more data to potentially fill up a bucket