Promise.all() is one of the most-used asynchronous APIs in JavaScript. Below is a quick reference on how to use it, then a mental model of how it works, and finally an interview challenge to implement it from scratch.
Promise.all is used in practicePromise.all() takes an iterable of values (usually Promises) and returns a single Promise. The returned promise fulfills when all input promises fulfill, with an array of the results in the same order as the input. It rejects immediately when any input rejects, with the reason of the first rejection.
The most common use is fetching data from multiple endpoints concurrently before rendering:
const [user, posts, tags] = await Promise.all([fetch('/api/user').then((r) => r.json()),fetch('/api/posts').then((r) => r.json()),fetch('/api/tags').then((r) => r.json()),]);
All three requests start at the same time. The total wait is the duration of the slowest request, not the sum of all three. Other typical use cases:
getUser() and getFeatureFlags() both succeeding before proceeding.If you want partial results when some calls fail (e.g., dashboard widgets where one slow endpoint shouldn't blank the screen), reach for Promise.allSettled instead.
Promise.all works under the hoodA useful one-paragraph mental model before looking at the implementation:
Promise.allwalks the input iterable, attaching a.thenhandler to each value (wrapping non-promise values withPromise.resolvefirst). It tracks completions with a counter and stores results in an array indexed by the original position. When the counter reaches zero, the outer promise fulfills with the results array. The first rejection short-circuits the outer promise; remaining inputs continue running but their results are discarded.
A few specifics worth remembering, since they are commonly misstated:
Promise.all([]) resolves synchronously with []. It does not reject.AbortController signal into each request.Implement your own version of Promise.all, called promiseAll, except that the function takes an array instead of a generic iterable. Be sure to read the description carefully and implement accordingly.
// Resolved example.const p0 = Promise.resolve(3);const p1 = 42;const p2 = new Promise((resolve, reject) => {setTimeout(() => {resolve('foo');}, 100);});await promiseAll([p0, p1, p2]); // [3, 42, 'foo']
// Rejection example.const p0 = Promise.resolve(30);const p1 = new Promise((resolve, reject) => {setTimeout(() => {reject('An error occurred!');}, 100);});try {await promiseAll([p0, p1]);} catch (err) {console.log(err); // 'An error occurred!'}
This is an important question to practice because async programming is frequently tested during interviews. Understanding how Promise.all works under the hood also makes related helpers like Promise.race, Promise.any, and Promise.allSettled easier to follow.
The working picture for Promise.all() is "fan out every input, then join once all of them fulfill". The code should start observing every item immediately, store each fulfilled value at its original index, and resolve the outer promise only when every input has fulfilled.
The recommended interview answer exposes the coordination state directly:
results: an array whose indexes match the input indexes.unresolved: how many inputs still need to fulfill before the outer promise can resolve.reject: the rejection path used by any input that rejects.Handle these pieces:
Promises are meant to be chained, so the function needs to return a Promise.Promise resolves with an empty array.Promise contains an array of resolved values in the same order as the input if all of them are fulfilled.Promise rejects immediately if any input value rejects or throws an error.Promises.asyncSince the function needs to return a Promise, construct a promise at the top level and do the coordination inside its executor.
The first special case is the empty input. With nothing to wait for, Promise.all([]) resolves with [].
For non-empty inputs, attempt to resolve every item in the input array. This can be achieved using Array.prototype.forEach or Array.prototype.map. Completion order does not matter because each item writes to results[index], not to the end of the array. After each fulfillment, decrement unresolved; when it reaches 0, the results array is complete and can be used to resolve the outer promise.
One thing to note here is that because the input array can contain non-Promise values, if they are not awaited, wrap each value with Promise.resolve(). This allows .then() on each item without differentiating between promises and non-promises.
Lastly, if any value rejects or throws while being awaited, reject the top-level promise immediately without waiting for other pending inputs. The remaining inputs are not canceled, but once the outer promise is settled, later calls to resolve or reject cannot change its state.
/*** @param {Array} iterable* @return {Promise<Array>}*/export default function promiseAll(iterable) {return new Promise((resolve, reject) => {// Preserve input order even if the promises settle in a different order.const results = new Array(iterable.length);let unresolved = iterable.length;if (unresolved === 0) {resolve(results);return;}iterable.forEach(async (item, index) => {try {const value = await item;results[index] = value;unresolved -= 1;if (unresolved === 0) {resolve(results);}} catch (err) {reject(err);}});});}
Promise.thenHere is an alternative version that uses Promise.then() instead of async/await. The state shape is the same: normalize each item, write the fulfilled value at the input index, decrement the unresolved counter, and use the second .then() callback to route rejections to the outer promise.
type ReturnValue<T> = { -readonly [P in keyof T]: Awaited<T[P]> };export default function promiseAll<T extends readonly unknown[] | []>(iterable: T,): Promise<ReturnValue<T>> {return new Promise((resolve, reject) => {// Preserve input order even if the promises settle in a different order.const results = new Array(iterable.length);let unresolved = iterable.length;if (unresolved === 0) {resolve(results as ReturnValue<T>);return;}iterable.forEach((item, index) => {// `Promise.resolve()` lets plain values and promises share the same code path.Promise.resolve(item).then((value) => {results[index] = value;unresolved -= 1;if (unresolved === 0) {resolve(results as ReturnValue<T>);}},(reason) => {reject(reason);},);});});}
Once one of the Promise's resolving functions (resolve or reject) is called, the promise is in the "settled" state, and subsequent calls to either function can change neither the fulfillment value nor the rejection reason, nor can they change the eventual state from "fulfilled" to "rejected" or vice versa.
results records settlement order, not input order. Assign by index instead.for loop changes the intended concurrency semantics.await item or Promise.resolve(item) to normalize both.Promise.all() behavior. The first rejection should settle the outer promise.Promise values, they will still be part of the returned array if all the input values are fulfilled.Promises, how to construct one, and how to use them.for (const item of iterable) { results.push(await item); } implementation will fail cases where a later input rejects before an earlier one resolves, because the earlier await blocks observation of the later rejection until it returns.console.log() aparecerão aqui.