Hard

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

Hint 1

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.

Hint 2

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.

Hint 3

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.