Skip to content

Instantly share code, notes, and snippets.

@domenic
Last active February 10, 2020 17:05
Show Gist options
  • 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 }));
}
}
@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