When Promise.all fulfils, in what order do the values appear in the resulting array, and what happens to non-promise entries in the iterable you pass it?
answer
- slots, not a finish line
- index of the input decides everything
- destructuring the awaited array is safe
- Promise.resolve wraps whatever you pass
- empty input still fulfils
basics
~20 sPromise.all fulfils with values in the order of the input iterable, never in completion order, so index 0 always holds the first input's value. Non-promise entries are passed through Promise.resolve and appear unchanged at their own positions.
solid answer
~40 sThe result array is **positionally** matched to the input, not ordered by who finished first. `Promise.all` records each value into the slot of the entry it came from, so a promise that resolves last still lands at its original index — which is what makes array destructuring like `const [a, b] = await Promise.all([fa(), fb()])` safe. Anything in the iterable that is not a promise is run through `Promise.resolve`, so plain values appear as themselves and thenables are adopted; `Promise.all([1, Promise.resolve(2)])` fulfils with `[1, 2]`. An empty iterable fulfils immediately with `[]`. `Promise.allSettled` uses exactly the same positional mapping, which is why you can zip its result array back against your input list by index to say which item failed.
go deeper
Remember that the output array lines up with the input array position by position, so destructuring the awaited result is safe. Know that plain values mixed into the array simply come back as themselves.
Explain the mechanism — each entry writes into its own pre-assigned slot and a counter tracks how many are outstanding — and cover thenable adoption, the empty-iterable case, and that any iterable is accepted, not only arrays.
Draw the line between deterministic results and non-deterministic execution: side effects land in completion order even though values land in input order. Use the index correspondence in allSettled to attribute failures back to specific inputs in real batch jobs.
Weigh whether positional correspondence is the right contract for a large batch at all: for long-running fan-outs, a per-item identifier carried in the result or an async iterator that streams outcomes is more robust than an index into an array the caller must keep alive.
## Input order, not completion order The single most useful property of `Promise.all` is that the output array mirrors the **input iterable positionally**. Internally the combinator walks the iterable, and for entry number *n* it attaches a fulfilment handler that writes the value into slot *n* of a pre-sized result array, decrementing a counter of outstanding entries. When the counter hits zero the aggregate fulfils with that array. Nothing about the order in which the handlers fire influences where a value lands. ```js const slow = new Promise(res => setTimeout(() => res('slow'), 100)); const fast = Promise.resolve('fast'); Promise.all([slow, fast]).then(console.log); // ['slow', 'fast'] ``` `fast` settled first, but it still occupies index 1. This determinism is what makes destructuring idiomatic: ```js const [user, orders, settings] = await Promise.all([ fetchUser(id), fetchOrders(id), fetchSettings(id), ]); ``` If results came back in completion order this pattern would be a race condition, and every call site would need a tag to identify which value was which. ## Non-promise entries pass through `Promise.all` does not require its entries to be promises. Each entry goes through `Promise.resolve`, which has three cases: an actual native promise is returned as-is; a **thenable** (any object with a callable `then` method) is adopted, so its `then` is invoked and its outcome becomes the entry's outcome; anything else is wrapped in an already-fulfilled promise. ```js Promise.all([1, 'two', Promise.resolve(3)]).then(console.log); // [1, 'two', 3] ``` The practical value is that you can mix cached-or-fetched values without special-casing: ```js const rows = ids.map(id => cache.get(id) ?? fetchRow(id)); const all = await Promise.all(rows); // cached entries need no wrapping ``` Note that a wrapped plain value still fulfils asynchronously as far as your `then` callbacks are concerned — the aggregate never settles synchronously, even when every entry is a plain value. ## The empty case `Promise.all([])` fulfils with `[]`, and it does so as soon as it can rather than hanging. The same holds for `Promise.allSettled([])`. This matters whenever the batch is built at runtime: an empty input is a legitimate no-work case, not an error, so guard clauses that treat a zero-length result as a failure will misfire. ## Any iterable, not just arrays The argument is an iterable, so a `Set`, a `Map`'s `values()`, a generator, or the result of `array.map()` all work. Order is the **iteration order** of that iterable, which is stable for arrays and Sets. Passing a non-iterable rejects the returned promise with a `TypeError` rather than throwing synchronously, so a `.catch` still sees it. ## The same mapping in allSettled `Promise.allSettled` preserves the same index correspondence, which is what makes error attribution possible: ```js const results = await Promise.allSettled(ids.map(id => fetchRow(id))); results.forEach((r, i) => { if (r.status === 'rejected') console.error(`row ${ids[i]} failed`, r.reason); }); ``` Without the guarantee that `results[i]` corresponds to `ids[i]`, you would have to bake the identifier into every rejection reason yourself. ## Where the ordering guarantee stops Ordering applies to the *results*, not to the *work*. The promises were created before the combinator ever saw them, so the underlying operations were already started, in whatever order the runtime and the host got to them, and they complete in whatever order they complete. `Promise.all` neither sequences them nor throttles them; it just files the answers into stable slots. Any observable side effects the operations perform — writes, log lines, counters — happen in completion order, and code that depends on those landing in array order will be wrong.
- If ordering is preserved, does that mean Promise.all imposes any ordering on when the operations actually run?No. The promises were already created — and therefore the work already started — before `Promise.all` was called. It only observes them. Side effects such as database writes or log output still happen in completion order, and the combinator neither sequences nor limits them. Only the *result slots* are deterministic.
- What happens if you pass Promise.all an object with a then method that is not a real promise?It is treated as a thenable: `Promise.resolve` adopts it, calls its `then` with resolve/reject functions, and whatever it calls first becomes that slot's outcome. This is how promises from older libraries interoperate. A misbehaving thenable that calls neither leaves the aggregate pending forever, and one that throws inside `then` rejects that slot.
saying these in an interview costs you the question
- Says results come back in completion order
- Thinks non-promise entries are dropped or rejected
- Believes Promise.all([]) hangs or rejects
- Claims Promise.all runs the operations one after another
- Assumes destructuring the result needs a sort first