Perform actions as promises get fulfilled using Promise.all()
I know it's not native, but with bluebird you can use Promise.some
(to fullfill after count
promises have been fulfilled) or Promise.mapSeries
(to fullfill promises in series) to somehow achieve the flow you expect.
Bluebird API
You cannot achieve this order using Promise.all
because the promise returned from Promise.all
waits for all promises from the provided array to resolve concurrently (as opposed to sequentially) before it itself resolves.
Instead, you can individually create the promises and fire of their requests concurrently:
// create promises and make concurrent requests
const s1 = getStuff(1, 200);
const s2 = getStuff(2, 100);
const s3 = getStuff(3, 250);
// ...
Then create a reaction chain on how to process them (stuff1 before stuff2, stuff2 before stuff3, etc.)
// create a chain of reaction order to the results of parallel promises
s1
.then(console.log) // s1 resolved: log result
.then(() => s2) // chain s2
.then(console.log) // s2 resolved: log result
.then(() => s3) // chain s3
// ...
.then(() => { // chain another function at at the end for when all promises resolved
// all promises resolved (all data was logged)
}
To react to promise results in the same order the promises were created, you can change your getStuff
function to dynamically chain the reactions using Array.prototype.reduce
:
var times = [200, 100, 250, 200, 300, 250, 5000];
var getStuff = function(time, index) { // swap the order of arguments so number is the index passed in from Array.map
return new Promise((resolve, reject) => {
window.setTimeout(() => {
resolve(`${index + 1} - Done.`); // use index + 1 because indexes start at 0
}, time);
});
};
times
// map each time to a promise (and number to the index of that time + 1) and fire of a request
.map(getStuff)
// dynamically build a reaction chain for the results of promises
.reduce((chain, promise) => {
return chain
.then(() => promise)
.then(console.log);
}, Promise.resolve())
.then(() => {
// all promises resolved (all data was logged in order)
});
nem035's answer is spot on. I want to point out that typically in this case you want to take the same action when requests happen and another action when they're all done.
You can use .all
for that with .map
:
Promise.all([ getStuff(1, 200),
getStuff(2, 100),
getStuff(3, 250),
getStuff(4, 200),
getStuff(5, 300),
getStuff(6, 250),
getStuff(7, 5000)]
.map(request => request.then(v => {
console.log("Request done! Got," v); // or some action per request
return v;
})).then(data => console.log(data));
You can boss this further with .map
by using the fact you're using the same function for every request:
Promise.all([[1, 200],
[2, 100],
[3, 250],
[4, 200],
[5, 300],
[6, 250],
[7, 5000]])
.map((a, b) => getStuff(a, b))
.map(request => request.then(v => {
console.log("Request done! Got," v); // or some action per request
return v;
})).then(data => console.log(data));
And further to:
Promise.all([200, 100, 250, 200, 300, 250, 5000])
.map((a, i) => getStuff(a, i + 1))
.map(request => request.then(v => {
console.log("Request done! Got," v); // or some action per request
return v;
})).then(data => console.log(data));
Or with bluebird:
const sideEffect = v => console.log("Got partial result", v));
const data = [200, 100, 250, 200, 300, 250, 5000];
Promise.map(data, (a, i) => getStuff(a, i + 1).tap(sideEffect))
.then(data => console.log(data));
Of course - you should just fix your backend, it's entirely unreasonable to ask the client to make 7 requests for different parts of the data - have the backend take ranges.