promise.any()
Prompt
Implement a function customPromiseAny that takes an array of promises and returns a new promise. It should behave like the native Promise.any() but you cannot use Promise.any in your implementation.
Playground
Think of Promise.any as the opposite of Promise.all.
Instead of needing ALL to succeed, you only need ONE to
succeed. The .then() handler on each promise should
immediately resolve the outer promise with that value.
Rejections don't end things early (unlike Promise.race).
Instead, count how many promises have rejected. Store each
rejection reason in an array using errors[index] = error.
When the rejection counter equals the array length, every
promise has failed. At that point, reject the outer
promise with new AggregateError(errors, 'All promises were rejected'). For an empty array, reject immediately
with an AggregateError too.
Solution
Explanation
If you've solved Promise.all, think of Promise.any as its mirror image.
Promise.all says: "I need ALL of you to succeed. If even one of you fails, I fail."
Promise.any says: "I only need ONE of you to succeed. I only fail if ALL of you fail."
How the solution works
The structure is very similar to Promise.all, but flipped:
function customPromiseAny(promises) {
const errors = [];
let rejectedCount = 0;
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(resolve)
.catch((error) => {
errors[index] = error;
rejectedCount++;
if (rejectedCount === promises.length) {
reject(
new AggregateError(
errors,
'All promises were rejected'
)
);
}
});
});
});
}In Promise.all, we counted successes and called resolve when ALL succeeded. Here, we count failures and call reject when ALL failed.
The .then(resolve) on each promise means the first promise to fulfill immediately resolves the outer promise. Just like Promise.race, the first success wins and everything else is ignored. But unlike Promise.race, rejections don't end things. They just bump the counter. Only when every single promise has rejected do we give up.
What is AggregateError?
When all promises reject, we can't just reject with one error because there are multiple errors (one from each promise). JavaScript has a special error type for this: AggregateError. It holds an array of individual errors inside it.
new AggregateError(
['Error 1', 'Error 2', 'Error 3'],
'All promises were rejected'
);The first argument is the array of errors, the second is a message. You can access the individual errors with error.errors.
Promise.any vs Promise.race
These two are easy to confuse. The difference is how they handle rejections:
- Promise.race: First settlement wins, period. If the first thing to happen is a rejection, the race rejects.
- Promise.any: First success wins. Rejections are ignored as long as at least one promise eventually succeeds. It only rejects when there are zero successes.