Skip to content

Instantly share code, notes, and snippets.

@domenic
Last active February 10, 2020 17:05
Show Gist options
  • Star 20 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save domenic/95e689d0be5e24fb08ec to your computer and use it in GitHub Desktop.
Save domenic/95e689d0be5e24fb08ec to your computer and use it in GitHub Desktop.
XHR-esque progress events on top of streams
function processBodyChunkwiseWithProgress(res, processChunk) {
const dummyEventTarget = document.createElement("div"); // why isn't EventTarget constructible? :(
const lengthComputable = res.headers.has("Content-Length");
const total = res.headers.get("Content-Length") || 0;
let loaded = 0;
// Using http://underscorejs.org/#throttle
const fireProgressThrottled = _.throttle(fireProgress, 50, { trailing: false });
fireProgress("loadstart");
pump();
return dummyEventTarget;
function pump() {
return res.body.read().then(({ value, done }) => {
if (done) {
fireProgress();
fireProgress("load");
fireProgress("loadend");
return;
}
fireProgressThrottled();
processChunk(value);
return pump();
})
.catch(() => {
fireProgress("error");
fireProgress("loadend");
});
}
function fireProgress(name = "progress") {
dummyEventTarget.dispatchEvent(new ProgressEvent(name, { loaded, total, lengthComputable }));
}
}
function writeToStreamWithProgress(arrayOfChunks, dest) {
const dummyEventTarget = document.createElement("div"); // why isn't EventTarget constructible? :(
const total = arrayOfChunks.reduce((soFar, chunk) => soFar + chunk.byteLength, 0);
let loaded = 0;
// Using http://underscorejs.org/#throttle
const fireProgressThrottled = _.throttle(fireProgress, 50, { trailing: false });
fireProgress("loadstart");
// Stream mechanism will take care of queuing these up and writing them in order for us
for (const chunk of arrayOfChunks) {
dest.write(chunk).then(() => {
loaded += chunk.byteLength;
fireProgressThrottled();
});
}
dest.closed.then(
() => {
fireProgress();
fireProgress("load");
fireProgress("loadend");
},
() => {
fireProgress("error");
fireProgress("loadend");
}
);
return dummyEventTarget;
function fireProgress(name = "progress") {
dummyEventTarget.dispatchEvent(new ProgressEvent(name, { loaded, total, lengthComputable: true }));
}
}
@jondediego
Copy link

Hi Domenic,
Thank you for this great work. But this can only be used for download progress or could it also be used in order to manage upload progress?
Thanks,

@o-t-w
Copy link

o-t-w commented Apr 20, 2018

Now that EventTarget is constructable & extendable in Chrome, how would you rewrite this? Is creating a new ProgressEvent any different from using new CustomEvent?


function processBodyChunkwiseWithProgress(res, processChunk) {
  const eventTarget = new EventTarget();

  const lengthComputable = res.headers.has("Content-Length");
  const total = res.headers.get("Content-Length") || 0;
  let loaded = 0;
  
  // Using http://underscorejs.org/#throttle
  const fireProgressThrottled = _.throttle(fireProgress, 50, { trailing: false });

  fireProgress("loadstart");
  pump();
  return eventTarget;
  
  function pump() {
    return res.body.read().then(({ value, done }) => {
      if (done) {
        fireProgress();
        fireProgress("load");
        fireProgress("loadend");
        return;
      }

      fireProgressThrottled();
      processChunk(value);
      
      return pump();
    })
    .catch(() => {
      fireProgress("error");
      fireProgress("loadend");
    });
  }
  
  function fireProgress(name = "progress") {
    eventTarget.dispatchEvent(new ProgressEvent(name, { loaded, total, lengthComputable }));
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment