JavaScript interview question
Compare Promise.all, Promise.allSettled, Promise.race and Promise.any. When would you use each in test code?
Short answer
Promise.all fulfils with all results or rejects on the first failure. allSettled waits for every promise and reports each outcome. race settles with the first promise to settle, success or failure. any fulfils with the first success and rejects with an AggregateError only if all fail.
Answer
All four take an iterable of promises and return a single promise. They differ in when they settle and what they report.
| Method | Fulfils when | Rejects when | Result |
|---|---|---|---|
Promise.all |
all fulfil | any rejects (immediately) | array of values, input order |
Promise.allSettled |
all settle | never | array of { status, value } / { status, reason } |
Promise.race |
first settles with success | first settles with failure | that single outcome |
Promise.any |
first fulfils | all reject | first value, or AggregateError |
None of them cancels the other promises. Work that was started keeps running after the combined promise settles. If you need cancellation, pass an AbortSignal to the underlying operations.
Promise.all: parallel setup that must all succeed
const [user, product, coupon] = await Promise.all([
api.createUser(),
api.createProduct(),
api.createCoupon(),
]);
Results arrive in input order, regardless of completion order. If any call fails, await throws that first error and the test stops, which is exactly what you want for mandatory setup. Watch out: the other requests may still succeed, so cleanup must handle partially created data.
Promise.allSettled: cleanup and bulk checks
const results = await Promise.allSettled(
createdIds.map((id) => api.delete(`/orders/${id}`)),
);
const failures = results.filter((r) => r.status === 'rejected');
if (failures.length) {
console.warn(`${failures.length} cleanups failed`, failures.map((f) => f.reason));
}
For teardown you want to attempt everything and report what failed, not stop at the first error. It is also useful for health-checking many endpoints and reporting all broken ones at once.
Promise.race: timeouts
function withTimeout(promise, ms) {
let timer;
const timeout = new Promise((_, reject) => {
timer = setTimeout(() => reject(new Error(`Timed out after ${ms}ms`)), ms);
});
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
}
Clearing the timer matters: otherwise a pending timer can keep a Node process alive or fire after the test ends. Remember that the original operation is not stopped. For fetch, AbortSignal.timeout(ms) is cleaner because it actually aborts the request.
Promise.any: first healthy source
const baseUrl = await Promise.any(
mirrors.map(async (url) => {
const res = await fetch(`${url}/health`);
if (!res.ok) throw new Error(`${url} unhealthy`);
return url;
}),
);
Only if every mirror fails does it reject, with an AggregateError whose errors array holds each reason. Promise.any requires ES2021 (Node 15+).
What interviewers look for
- Correct settle and reject semantics for each, especially
raceversusany. - Concrete testing use cases: parallel setup, resilient cleanup, timeouts.
- Knowing promises are not cancelled, and mentioning
AbortController. - Handling partial failure in
Promise.allsetups.
A strong answer covers
- Promise.all: all results in order, rejects fast on first failure
- allSettled: waits for every promise, returns status/value/reason objects
- race: first to settle, either outcome; typical timeout pattern
- any: first fulfilment, AggregateError if all reject
- Notes that other promises keep running; they are not cancelled