for await...of
Loops over an async iterable, awaiting each value before running the loop body.
for await (variable of iterable)
statement- Added in JavaScript ECMAScript 2018 (ES9); widely available in browsers since January 2020
Parameters
| Parameter | Type | Description |
|---|---|---|
$variable | declaration | assignment target | Receives a value from the sequence on each iteration. May be a const, let or var declaration, or an assignment target such as an object property or a destructuring pattern. Variables declared with var are not local to the loop. |
$iterable | AsyncIterable | Iterable | An async iterable or a sync iterable. The source of the sequence of values. |
$statement | statement | A statement executed on every iteration, which may reference variable. Use a block to run several statements. |
Return value
undefinedNothing. The loop runs the statement once per value produced by the iterator.
Errors
Throws a TypeError if the operand is neither async iterable nor sync iterable, a SyntaxError if used where await is not allowed, and re-throws whatever the iterator's promise rejects with.
Changelog
- PHP ES2018Introduced together with the async iteration protocol (Symbol.asyncIterator) and async generator functions.
for await...of is the loop form of await. On each turn it calls the iterator's next(), awaits the promise that comes back, and binds the resulting value before running the body. It looks for [Symbol.asyncIterator]() first and falls back to [Symbol.iterator](), wrapping a sync iterator so that promises it yields are resolved rather than handed to you unresolved.
When to reach for it
- Consuming an async generator that produces values over time — pages of results, batches, ticks.
- Any source where the next item is not known until the previous one has been processed, so
Promise.all()cannot be used. - Turning a push-style source into pull-style code you can
breakout of.
What trips people up
The loop is deliberately serial. It never overlaps iterations, so it is the wrong tool for a fixed array of independent promises — use Promise.all() there. It is also only valid where await is: inside an async function body or at a module's top level.
The sharpest edge is error handling with a sync generator that yields a rejected promise. The loop throws immediately and the generator's finally block never runs, so cleanup is skipped. Iterating with plain for...of and awaiting each value yourself restores it.
Example
When to use it
- Consume paginated API results one page at a time until a cursor runs out
- Process a queue where each job's outcome decides whether to fetch the next
- Read a chunked source and stop early with break once a match is found
- Drive a polling loop that yields a value on every tick
Frequently asked questions
What is the difference between for await...of and for...of?
for await...of awaits each value before running the body, so promises are unwrapped and async iterators are supported; for...of binds whatever the iterator yields, which means you get the Promise object itself. Only for await...of consults Symbol.asyncIterator.
Can I use for await...of to run promises in parallel?
No — it is strictly serial and never overlaps iterations. For a fixed collection of independent promises use Promise.all or Promise.allSettled; reach for for await...of when each value must arrive before the next can be requested.
Why is my generator's finally block skipped?
Because a sync generator that yields a rejected promise makes for await...of throw before the generator is resumed, so its finally never runs. Loop with for...of and await each value yourself, or make the generator an async generator so the rejection happens inside it.
More examples
Sync iterable of promises is resolved for you
for await...of unwraps each yielded promise before binding it, so the values print as 0, 1, 2. The plain for...of loop hands you the promise object itself for the middle value, which is the usual reason a logged item shows up as a pending Promise.
function* mixed() {
yield 0;
yield Promise.resolve(1);
yield 2;
}
for await (const n of mixed()) {
console.log("for await:", n); // 0, 1, 2
}
for (const n of mixed()) {
console.log("for of:", n); // 0, Promise, 2
}break runs the generator's cleanup
Prints 0, 1, 2, then "cleanup ran", then "after loop". Leaving the loop early calls the iterator's return() method and awaits it, which resumes the generator inside its finally block — so an infinite generator is safe to break out of as long as cleanup lives there.
async function* source() {
try {
let i = 0;
while (true) yield i++;
} finally {
console.log("cleanup ran");
}
}
for await (const n of source()) {
console.log(n);
if (n === 2) break;
}
console.log("after loop");Paginating until the cursor runs out
Prints a, b, c. Each page must arrive before the next cursor is known, so the work genuinely cannot be parallelised — this is the case for await...of exists for. The consumer sees a flat sequence of items and never handles cursors.
const pages = { 0: { items: ["a", "b"], next: 1 }, 1: { items: ["c"], next: null } };
const getPage = async (c) => pages[c];
async function* allItems() {
let cursor = 0;
while (cursor !== null) {
const page = await getPage(cursor);
yield* page.items;
cursor = page.next;
}
}
for await (const item of allItems()) console.log(item);
Discussion