for await...of

Loops over an async iterable, awaiting each value before running the loop body.

Signature
for await (variable of iterable)
  statement
  • Added in JavaScript ECMAScript 2018 (ES9); widely available in browsers since January 2020

Parameters

ParameterTypeDescription
$variabledeclaration | assignment targetReceives 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.
$iterableAsyncIterable | IterableAn async iterable or a sync iterable. The source of the sequence of values.
$statementstatementA 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 break out 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

Try it yourself
Loading editor…
Press Run to execute the code.

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.

Example · javascript
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.

Example · javascript
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.

Example · javascript
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

  • Be the first to comment on this lesson.