Implementing Promise.all, allSettled, race and any from scratch

Ever been in a Frontend interview where the interviewer asks you to explain all the Promise methods, in particular, .all, .allSettled, .race, .any? Sometimes they just ask for the definition. Sometimes, a code example of each one.


Let me give you some examples of what I typically do when I'm asked about them.


Promise.all

Nailing this one is a good sign, it's usually the first they ask. The definition itself is quite simple, but the implementation can get tricky.


Before diving into code, let's break it down:

  • Promise.all takes an array of promises
  • Returns a single Promise that only resolves when all of them are resolved
  • If any of the promises are rejected, the whole thing is rejected immediately as well (aka fail-fast)
  • Keeps the original order of results, regardless of which Promise finished first
promiseAll()
function promiseAll(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) reject('Must be a valid array');
// Edge case: empty array resolves immediately
if (promises.length === 0) resolve([]);
const results = new Array(promises.length);
let resolved = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise).then(value => {
results[index] = value; // important to use index to preserve the order (avoid using .push())
resolved++;
if (resolved === promises.length) {
resolve(results);
}
}).catch(reject);
});
});
}


A common mistake here is using .push() instead of index assignment. This matters because depending on whether the last promise is faster than the other ones, it will be return first, for example:

promiseAll: output
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
promiseAll([p1, p2, p3]).then(console.log); // [1, 2, 3]
// if promiseAll uses .push() it could return them in the wrong order: [3, 1, 2] instead of [1, 2, 3]
Note: If any promise rejects, promiseAll will call reject immediately via the .catch handler.

Promise.allSettled

Kind of similar to Promise.all , the main differences are what is returned and the fact that it doesn't stop at the first rejection.It always waits for every promise to settle and returns each result as either fulfilled or rejected.

Let's take a look at it:

promiseAllSettled()
function promiseAllSettled(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) reject('must be a valid array');
if (promises.length === 0) resolve([]);
const results = new Array(promises.length);
let settled = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise).then(value => {
results[index] = {
status: 'fulfilled',
value,
}
}).catch(reason => {
results[index] = {
status: 'rejected',
reason,
}
}).finally(() => {
if (++settled === promises.length) resolve(results)
})
})
})
}

If you test allSettled you will see something like this:

promiseAllSettled(): output
const p1 = Promise.resolve(1);
const p2 = Promise.reject('oops!');
const p3 = Promise.resolve(3);
promiseAllSettled([p1, p2, p3]).then(console.log);
// Output:
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: 'oops!' },
// { status: 'fulfilled', value: 3 }
// ]


In summary the difference between .all and .allSettled is simple:

  • Promise.all — fails fast. One rejection and it's over.
  • Promise.allSettled — waits for everyone. No matter what happens, it collects all results and resolves at the end. It never rejects.

Promise.race

Promise.race is definitely the simplest one to understand, no tricks, no deep logic. You just need to return the first finished promise, whether it resolves or rejects.

promiseRace()
function promiseRace(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) reject('must be a valid array');
promises.forEach(promise => {
Promise.resolve(promise).then(resolve).catch(reject);
})
})
}

That's it. Nothing else. It's kind of a race, whichever Promise finishes first wins, and that's the result you get. The rest are ignored. Simple enough, right?

promiseRace(): output
const slow = new Promise(resolve => setTimeout(() => resolve('slow'), 1000));
const fast = new Promise(resolve => setTimeout(() => resolve('fast'), 100));
const medium = new Promise(resolve => setTimeout(() => resolve('medium'), 500));
promiseRace([slow, fast, medium]).then(console.log); // 'fast'
// Rejection wins if it's the fastest
const quickFail = new Promise((_, reject) => setTimeout(() => reject('oops'), 50));
promiseRace([slow, fast, quickFail]).catch(console.error); // 'oops'

Promise.any

Last but not least: .any. Think of it as the opposite of .all. Where .all fails if anyone fails, .any only fails if everyone fails. Otherwise, it keeps going until it finds the first resolved one.

promiseAny()
function promiseAny(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) reject('must be a valid array');
if (promises.length === 0) resolve([]);
const errors = new Array(promises.length);
let rejected = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise).then(resolve).catch(reason => {
errors[index] = reason;
if (++rejected === promises.length) reject(errors);
// you can also return errors using new AggregateError()
})
})
})
}

So, finally, our output should be something like this:

promiseAny(): output
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
promiseAny([p1, p2, p3]).then(console.log); // p1 => 1
const allPromises = [
Promise.reject('nope'),
Promise.reject('nah'),
Promise.reject('never'),
];
promiseAny(allPromises).catch(err => {
console.log(err); // ['nope', 'nah', 'never']
});

In short

all needs everyone to succeed. allSettled just needs everyone to finish. race takes whoever is fastest, whether it resolves or rejects. any takes the first win and only gives up when there's nothing left to win.

Back to Blog