Waiting for Several Things at Once
Promise.all, Promise.allSettled, Promise.race and Promise.any start several slow jobs together and wait for all of them, every answer, the first to finish or the first success.
Freelancer · JavaScript · Updated
After this lesson you can
- Run several promises at once with Promise.all and read their results
- Choose between Promise.all, Promise.allSettled, Promise.race and Promise.any for a job
Key takeaways
- Awaiting one promise after another takes as long as all of them added up. Start them all first, and the wait is only as long as the slowest.
- Promise.all fulfills with every value, in the order you gave the promises, and rejects as soon as one of them rejects.
- Promise.allSettled waits for every promise and reports each one as fulfilled with its value or rejected with its reason.
- Promise.race settles like the first promise to settle, success or failure. Promise.any fulfills with the first success.
- Promise.any rejects only when every promise rejects, with an AggregateError that lists every reason.
A dashboard loads the weather, your calendar and the news. A shop's checkout asks three shipping companies for a price. A music app tries two servers and plays from whichever answers first. In each case, waiting for one answer before asking the next would make people stare at a spinner for no reason. JavaScript has four tools for waiting on several promises at once, and picking the right one is the difference between an app that feels instant and one that feels stuck.
One at a time is slow
A table of three orders three pizzas. Here's the kitchen cooking them with await, one after another. Date.now() gives the time in milliseconds, so the difference between two readings is how long the code took:
function cook(pizza, ms) {
return new Promise((resolve) => setTimeout(() => resolve(`${pizza} ready`), ms));
}
async function oneAtATime() {
const start = Date.now();
const first = await cook('Margherita', 300);
const second = await cook('Pepperoni', 300);
const third = await cook('Veggie', 300);
console.log(first, second, third);
console.log(`Took about ${Math.round((Date.now() - start) / 100) / 10} seconds`);
}
oneAtATime();
Margherita ready Pepperoni ready Veggie ready
Took about 0.9 seconds
The Pepperoni doesn't go in the oven until the Margherita comes out. But the pizzas don't need each other: a real kitchen puts all three in the oven together. When jobs don't depend on each other's results, start them all, and then wait.
Promise.all: everything, or nothing
Promise.all (read "promise dot all") takes an array of promises and returns one promise. It's fulfilled when every promise in the array is, with an array of their values:
function cook(pizza, ms) {
return new Promise((resolve) => setTimeout(() => resolve(`${pizza} ready`), ms));
}
async function allAtOnce() {
const start = Date.now();
const pizzas = await Promise.all([
cook('Margherita', 300),
cook('Pepperoni', 100),
cook('Veggie', 200),
]);
console.log(pizzas);
console.log(`Took about ${Math.round((Date.now() - start) / 100) / 10} seconds`);
}
allAtOnce();
[ 'Margherita ready', 'Pepperoni ready', 'Veggie ready' ]
Took about 0.3 seconds
All three cooked at the same time, so the wait is the slowest pizza's 0.3 seconds, not 0.6. And the values come back in the order you gave the promises, not the order they finished: the Pepperoni was ready first, but it's second in the array, because it was second in the list. So you always know which result belongs to which order.
The catch: Promise.all is all or nothing. If any one promise is rejected, the whole thing is rejected at once, with that reason, and the other values are lost. That's right when you need every piece (a page that can't show without both the user and their settings), and wrong when one failure shouldn't spoil the rest.
Try it. Save it as table.js, add a fourth cook('Hawaiian', 500) to the array, and predict both lines before you run node table.js.
Promise.allSettled: every answer, good or bad
Promise.allSettled waits for every promise to settle, whatever happens, and reports on each one. Each report is an object with a status of 'fulfilled' and a value, or a status of 'rejected' and a reason:
function cook(pizza, ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (pizza === 'Mushroom') {
reject(new Error("We're out of mushrooms"));
} else {
resolve(`${pizza} ready`);
}
}, ms);
});
}
const table = [cook('Margherita', 300), cook('Mushroom', 100), cook('Veggie', 200)];
Promise.all(table)
.then((pizzas) => console.log(pizzas))
.catch((error) => console.log(`Promise.all gave up: ${error.message}`));
Promise.allSettled(table).then((results) => {
for (const result of results) {
console.log(result.status, result.status === 'fulfilled' ? result.value : result.reason.message);
}
});
Promise.all gave up: We're out of mushrooms
fulfilled Margherita ready
rejected We're out of mushrooms
fulfilled Veggie ready
Promise.all gave up after 0.1 seconds, as soon as the Mushroom failed. Promise.allSettled waited for the whole table and can tell the customers which two pizzas are coming. It's the one to reach for when you want a report: which of ten photos uploaded, which of five feeds loaded. allSettled never rejects.
Promise.race: the first to finish, win or lose
Promise.race settles the same way as the first promise to settle: fulfilled if the first one to finish worked, rejected if it failed. Its everyday job is a time limit. Race the real work against a timer that rejects:
function deliver(order, ms) {
return new Promise((resolve) => setTimeout(() => resolve(`${order} delivered`), ms));
}
function giveUpAfter(ms) {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error('Delivery is taking too long')), ms);
});
}
async function deliverWithin(limit) {
try {
const message = await Promise.race([deliver('Order 12', 400), giveUpAfter(limit)]);
console.log(message);
} catch (error) {
console.log(error.message);
}
}
deliverWithin(600).then(() => deliverWithin(200));
Order 12 delivered
Delivery is taking too long
With a 600 millisecond limit, the delivery wins the race. With 200, the timer wins, and its rejection is what await throws. In (_, reject) =>, the _ (read "underscore") is a name for a parameter the function doesn't use: this promise never resolves, it only rejects.
Promise.any: the first success
Promise.any waits for the first promise that's fulfilled, and ignores failures along the way. Phone three drivers at once, and take the first one who says yes. Watch how race and any treat the same three phone calls, when Sam answers first, but only to say he's busy:
function askDriver(name, minutes, busy) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (busy) {
reject(new Error(`${name} is busy`));
} else {
resolve(`${name} can deliver in ${minutes} minutes`);
}
}, minutes * 10);
});
}
const askEveryone = () => [askDriver('Sam', 8, true), askDriver('Priya', 12, false), askDriver('Leo', 20, false)];
Promise.race(askEveryone())
.then((answer) => console.log(`race: ${answer}`))
.catch((error) => console.log(`race: ${error.message}`));
Promise.any(askEveryone())
.then((answer) => console.log(`any: ${answer}`))
.catch((error) => console.log(`any: ${error.message}`));
race: Sam is busy
any: Priya can deliver in 12 minutes
race stopped at the first answer, a no. any waited for the first yes. Promise.any is rejected only when every promise is rejected, and then its reason is an AggregateError: one error holding all the others, in its errors array. Ask only Sam and Ana, who are both busy, and error.errors.map((reason) => reason.message) gives [ 'Sam is busy', 'Ana is busy' ].
Which one fits?
| You need | Use | It's rejected when |
|---|---|---|
| Every result, and one failure spoils the whole job | Promise.all |
any one is rejected (at once) |
| A report on every job, good and bad | Promise.allSettled |
never |
| The first to finish, success or failure, such as a time limit | Promise.race |
the first to settle is rejected |
| The first success, ignoring failures | Promise.any |
every one is rejected |
One more thing all four share: they only wait. The jobs started the moment you called cook or askDriver; Promise.all and the others decide what to do with the answers.
Check yourself
2 questions to try on the lesson page.
So what
Real apps wait on many things at once, and these four tools let you say exactly how: all of it, a full report, a time limit, or the first that works. In the Exercise: Pizza Order Tracker, you'll use Promise.all for a whole table's order and Promise.any to find the fastest driver.