Asynchronous JavaScript
Async Iterators & for await...of
Master asynchronous iteration in JavaScript. Learn to implement Symbol.asyncIterator, write async generators, and process data streams using for await...of.
1. Introduction
Standard iterators deal with synchronous data. To iterate over collections where elements are retrieved asynchronously (like streaming data from a network or loading files in chunks), JavaScript provides the Async Iterator protocol and the for await...of loop.
2. Why It Matters
Async iterators allow you to process data streams (like Node.js readable streams or paginated API requests) element-by-element as the data arrives, instead of waiting for the entire dataset to download, saving memory.
3. Real-World Analogy
Think of a Conveyor Belt at a Baggage Claim:
- Standard Iterator (Synchronous Pile): All luggage is pre-unloaded and stacked in the lobby. You walk along the pile and inspect each bag. The bags are immediately available.
- Async Iterator (Moving Conveyor Belt): You stand at the belt. The belt moves. You must wait (await) for each bag to arrive sequentially. If there is a delay in the baggage room, you wait at the belt until the next bag emerges (resolves the next promise). You process bags one-by-one as they arrive.
4. The Async Iterator Protocol
An object is async iterable if it implements a method with the key [Symbol.asyncIterator]. This method must return an async iterator whose next() method returns a Promise that resolves to an iterator result: { value, done }.
5. Asynchronous Generators
The easiest way to write custom async iterators is by using Asynchronous Generators (declared using async function*). Inside an async generator, you can use both the yield keyword (to return values) and the await keyword (to pause execution for asynchronous tasks).
6. Practical Example
This script demonstrates streaming lines from a readable text stream using an async generator:
7. Common Mistakes
- Using standard for...of loops instead of for await...of: Standard
for...ofloops do not wait for the next Promise to resolve. Attempting to run them on async iterables returns Promise references directly or throws a TypeError. - Forgetting to handle stream terminations: Not setting the
done: trueflag inside your custom async iterator creates infinite loops that run indefinitely.
8. Quick Quiz
Q1: What does an async iterator's next() method return?
A) An object containing { value, done }
B) A Promise that resolves to an object containing { value, done }
Answer: B — The next() method of an async iterator returns a Promise that resolves to the standard { value, done } iteration result.
9. Scenario-Based Challenge
The Dynamic Log Tailer:
A Node.js server app monitors a local log file: watchLog(). When a new line is written, load the line asynchronously. Design an async generator that yields new log lines as they are written, allowing developers to process logs in real time.
10. Debugging Exercise
Identify and fix the syntax error in the following dynamic stream processor:
function* getMetricsStream() {
// Objective: Yield metrics retrieved from API page calls
const page1 = await fetch('/page1').then(r => r.json()); // syntax bug!
yield page1;
const page2 = await fetch('/page2').then(r => r.json());
yield page2;
}
View Solution
Diagnosis: You cannot use the await keyword inside a standard generator function (function*). To use await, you must declare it as an asynchronous generator function (async function*).
Fix: Add the async modifier before the generator function definition:
async function* getMetricsStream() {
const page1 = await fetch('/page1').then(r => r.json());
yield page1;
const page2 = await fetch('/page2').then(r => r.json());
yield page2;
}
11. Interview Questions
🟢 Q1: Compare standard generators with asynchronous generators.
Answer:
• Standard Generators (function*): Execute synchronously. The next() method returns a plain object: { value, done } immediately. You cannot use await inside the function body.
• Asynchronous Generators (async function*): Execute asynchronously. The next() method returns a Promise that resolves to { value, done }. You can use both the yield and await keywords inside the function body.
12. Production Considerations
- • Stream Backpressure: When iterating over fast data streams using
for await...of, ensure your processing logic in the loop is fast. If processing takes longer than the data arrival time, it can cause backpressure issues, consuming memory.