Async Iterators and for await...of

Difficulty: Advanced

Question

What are async iterators and async generators in JavaScript? When would you use them?

Answer

An async iterator implements the async iterator protocol: each call to next() returns a Promise that resolves to { value, done }.

for await...of loops consume async iterables, awaiting each item.

Async generators (async function*) combine generators and async/await - they can use both yield and await.

Use cases: - Streaming API responses chunk by chunk - Paginated API calls (fetch all pages lazily) - Processing large files line by line - Rate-limited API calls with built-in pausing

Code examples

Async Generator for Paginated API

async function* fetchPaginatedUsers(baseUrl) {
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const response = await fetch(`${baseUrl}?page=${page}`);
    const data = await response.json();
    yield* data.users;
    hasMore = data.hasNextPage;
    page++;
  }
}

async function processAllUsers() {
  for await (const user of fetchPaginatedUsers('/api/users')) {
    console.log('Processing:', user.name);
    if (user.name === 'Stop') break;
  }
}

const asyncRange = {
  from: 1, to: 5,
  [Symbol.asyncIterator]() {
    let current = this.from;
    const last = this.to;
    return {
      async next() {
        await new Promise(r => setTimeout(r, 100));
        return current <= last
          ? { value: current++, done: false }
          : { done: true };
      }
    };
  }
};

(async () => {
  const nums = [];
  for await (const num of asyncRange) nums.push(num);
  console.log(nums);
})();

Async generators pause between yields, awaiting network calls. Break early stops fetching further pages - lazy evaluation.

Streaming Response

async function* streamText(url) {
  const response = await fetch(url);
  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) return;
      yield decoder.decode(value, { stream: true });
    }
  } finally {
    reader.releaseLock();
  }
}

async function processStream() {
  let totalChars = 0;
  for await (const chunk of streamText('/api/large-file.txt')) {
    totalChars += chunk.length;
  }
  console.log(`Processed ${totalChars} characters`);
}

async function toArray(asyncIter) {
  const arr = [];
  for await (const item of asyncIter) arr.push(item);
  return arr;
}

ReadableStream from fetch is an async iterable. Async generators elegantly wrap streaming APIs.

Key points

Concepts covered

Async Iterator, for await...of, Async Generator, Streaming