In JavaScript, what does a `for await (const value of source)` loop do that a plain `for...of` loop does not, and what kinds of source can it consume?
answer
- asynchronous cousin of for...of
- awaits the value each turn
- two protocols, one falls back
- only where await is legal
basics
~20 sfor await...of awaits every step of the iteration. It consumes async iterables (objects with a Symbol.asyncIterator method) and also ordinary iterables, awaiting each produced value before the body runs, so the body sees settled values instead of promises.
solid answer
~50 s`for await...of` is the asynchronous form of `for...of`, added in ES2018. On each turn it calls the iterator's `next()`, awaits the result object, and awaits the `value` inside it, so the loop body always receives a settled value rather than a promise. It accepts two kinds of source. If the object has a `Symbol.asyncIterator` method, that async iterator drives the loop. If it does not, the engine falls back to `Symbol.iterator` and wraps the sync iterator so each yielded value is awaited — which is why `for await` over a plain array of promises hands you the resolved values in array order. If any awaited value rejects, the loop throws at that point, so a `try/catch` around the loop catches it. The construct is only legal where `await` is legal: inside an async function, an async generator, or at the top level of an ES module.
go deeper
Be able to say that the loop awaits each value for you, works on async iterables and on plain iterables of promises, and only compiles where await is allowed.
Explain the two lookup paths — Symbol.asyncIterator first, then a wrapped Symbol.iterator — and that the loop awaits both the result object from next() and the value inside it.
Show judgment about when ordered one-at-a-time consumption is the right shape at all, and be ready to say what it costs in latency versus overlapping the work.
Frame it as an API-design choice: exposing a source as an async iterable gives consumers natural pull-based flow control, while exposing a promise of an array forces the whole result into memory and forfeits incremental delivery.
## The one-sentence version `for await...of` is `for...of` with an `await` inserted at every point where the iteration protocol could produce a promise. It was added in ES2018 alongside async generators and the `Symbol.asyncIterator` well-known symbol. ## What one turn of the loop does A synchronous `for...of` loop calls `iterator.next()` and expects an object shaped `{ value, done }` back immediately. An asynchronous source cannot do that: it does not yet know whether there is a next value, because finding out requires I/O. So the async iteration protocol says `next()` returns a **promise** of `{ value, done }`. `for await...of` therefore does, per turn: 1. call `next()` on the iterator; 2. `await` what comes back, to get the `{ value, done }` object; 3. if `done` is true, exit the loop; 4. otherwise bind `value` to the loop variable and run the body; 5. repeat. Because step 2 is a real `await`, the loop **suspends the enclosing async function** between turns and yields to the event loop. Nothing after the loop runs until the source is exhausted. ## Where it is allowed The loop contains an implicit `await`, so it is subject to exactly the same placement rule as `await` itself: it is legal inside an `async function`, inside an `async function*`, and at the top level of an ES module (top-level await). Writing it in a plain synchronous function is a **syntax error**, not a runtime error — a common surprise when someone drops one into an array `forEach` callback, which is an ordinary non-async function. ## The two kinds of source When the loop starts, the engine looks for a `Symbol.asyncIterator` method on the source and calls it to get an async iterator. That is the primary path. If `Symbol.asyncIterator` is absent, the engine falls back to `Symbol.iterator` and wraps the resulting synchronous iterator in an adapter (the spec calls it an async-from-sync iterator). The adapter calls the sync `next()`, then awaits the `value` it produced before delivering it. That fallback is the reason this works: ```js const delay = (ms, v) => new Promise(r => setTimeout(() => r(v), ms)); async function run() { for await (const v of [delay(30, 'a'), delay(10, 'b'), 'c']) { console.log(v); // 'a', then 'b', then 'c' } } ``` An array is not an async iterable — it only has `Symbol.iterator` — yet the loop still prints resolved strings, in **array order**, not completion order. Non-promise values such as `'c'` pass through untouched, because awaiting a non-promise simply resolves to it after a microtask. ## Contrast with plain for...of Write the same array with `for...of` and each `v` is the promise object itself: ```js for (const v of [delay(30, 'a')]) { console.log(v); // Promise { <pending> } } ``` You would have to `await v` yourself inside the body. Note that this manual form is *not* equivalent to `for await` in one respect: `for await` also awaits the **result object** from `next()`, which is what makes genuinely asynchronous sources (ones that do not know their next value yet) work at all. An array always knows. ## Errors If `next()` rejects, or the value it produced rejects, the `await` throws inside the loop, the loop stops, and the exception propagates out of the loop like any other. So the natural shape is: ```js try { for await (const chunk of source) process(chunk); } catch (err) { // first failure that surfaced during iteration } ``` A rejection also terminates iteration — the loop does not skip the bad value and continue. If you want to keep going, the `try/catch` has to be *inside* the body around the work you do, and the source itself must not be the thing that failed. ## What it does not do It does not add concurrency. Each turn waits for the previous one. That is precisely its purpose — a sequential pull, one value at a time — and it is the opposite of a fan-out combinator that starts everything at once. Reach for it when values arrive over time (streamed chunks, pages of an API, events) and you want to handle each as it comes; reach for a fan-out when you have a fixed set of independent operations and want them overlapped.
- Why is `for await...of` a syntax error inside a plain callback passed to `array.forEach`?Because the loop contains an implicit `await`, and `await` is only legal inside an async function, an async generator, or a module's top level. A `forEach` callback is an ordinary function, so the parser rejects the loop outright. Marking the callback `async` fixes the syntax, but then `forEach` ignores the returned promise and does not wait for it — which is usually the real bug.
- If one value in the middle of the iteration rejects, does the loop skip it and continue?No. The rejection surfaces at the implicit `await`, so it throws out of the loop and iteration stops there. Values that would have come later are never requested. To survive a bad item you have to wrap the work inside the body in `try/catch`, and even that only helps if the failure came from your processing rather than from the source producing the value.
- Does `for await` over an array of promises give you values in completion order?No — in array order. The sync-iterator fallback pulls element 0, awaits it, delivers it, then pulls element 1. A fast promise sitting at index 2 still waits its turn behind a slow one at index 0. If you want completion order you need a different mechanism entirely; the loop's whole contract is ordered, one-at-a-time delivery.
saying these in an interview costs you the question
- Thinks for await...of runs the iterations in parallel
- Says it only works on async generators, not arrays
- Believes the loop body receives promises you must await yourself
- Thinks a rejected value is skipped and the loop continues
- Writes for await inside a non-async callback and expects it to parse