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 toPromise.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 handling0 / 0
progress events by avoiding the quotientNaN
.
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)}`);
});