ES6 Promise.all progress

You can add a .then() to each promise to count whos finished. something like :

var count = 0;

var p1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 5000, 'boo');
}); 
var p2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 7000, 'yoo');
}); 
var p3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 3000, 'foo');
}); 

var promiseArray = [
  p1.then(function(val) {
    progress(++count); 
    return val 
  }), 
  p2.then(function(val) {
    progress(++count); 
    return val 
  }), 
  p3.then(function(val) {
    progress(++count); 
    return val 
  })
]

function progress(count) {
  console.log(count / promiseArray.length);
}

Promise.all(promiseArray).then(values => { 
  console.log(values);
});

This has a few advantages over Keith's answer:

  • The onprogress() callback is never invoked synchronously. This ensures that the callback can depend on code which is run synchronously after the call to Promise.progress(...).
  • The promise chain propagates errors thrown in progress events to the caller rather than allowing uncaught promise rejections. This ensures that with robust error handling, the caller is able to prevent the application from entering an unknown state or crashing.
  • The callback receives a ProgressEvent instead of a percentage. This eases the difficulty of handling 0 / 0 progress events by avoiding the quotient NaN.
Promise.progress = async function progress (iterable, onprogress) {
  // consume iterable synchronously and convert to array of promises
  const promises = Array.from(iterable).map(this.resolve, this);
  let resolved = 0;

  // helper function for emitting progress events
  const progress = increment => this.resolve(
    onprogress(
      new ProgressEvent('progress', {
        total: promises.length,
        loaded: resolved += increment
      })
    )
  );

  // lift all progress events off the stack
  await this.resolve();
  // emit 0 progress event
  await progress(0);

  // emit a progress event each time a promise resolves
  return this.all(
    promises.map(
      promise => promise.finally(
        () => progress(1)
      ) 
    })
  );
};

Note that ProgressEvent has limited support. If this coverage doesn't meet your requirements, you can easily polyfill this:

class ProgressEvent extends Event {
  constructor (type, { loaded = 0, total = 0, lengthComputable = (total > 0) } = {}) {
    super(type);
    this.lengthComputable = lengthComputable;
    this.loaded = loaded;
    this.total = total;
  }
}

I've knocked up a little helper function that you can re-use.

Basically pass your promises as normal, and provide a callback to do what you want with the progress..

function allProgress(proms, progress_cb) {
  let d = 0;
  progress_cb(0);
  for (const p of proms) {
    p.then(()=> {    
      d ++;
      progress_cb( (d * 100) / proms.length );
    });
  }
  return Promise.all(proms);
}

function test(ms) {
  return new Promise((resolve) => {
    setTimeout(() => {
       console.log(`Waited ${ms}`);
       resolve();
     }, ms);
  });
}


allProgress([test(1000), test(3000), test(2000), test(3500)],
  (p) => {
     console.log(`% Done = ${p.toFixed(2)}`);
});