Async Iterators
HardAsync iterators extend the iterator protocol to handle asynchronous data sources. Using Symbol.asyncIterator and async generator functions (async function*), you can consume paginated APIs, streaming data, and event sources with the for-await-of loop.
Interactive Visualization
for...in vs for...of
Step 1: Setup
const obj = { a: 1, b: 2 };
const arr = [10, 20, 30];Output
No output yet
1 / 5
Key Points
- Symbol.asyncIterator defines the async iteration protocol on objects
- Async generators (async function*) combine generators with async/await
- for-await-of loop consumes async iterables, awaiting each yielded promise
- Each next() call returns a Promise<{ value, done }>
- Ideal for paginated APIs, streaming responses, and server-sent events
- Can be combined with AbortController for cancellable async iteration
Code Examples
Basic Async Generator
async function* asyncRange(start, end) { for (let i = start; i <= end; i++) { await new Promise(resolve => setTimeout(resolve, 100)); yield i; } } async function main() { for await (const num of asyncRange(1, 5)) { console.log(num); // 1, 2, 3, 4, 5 (each after 100ms) } }
Async generators yield values asynchronously. for-await-of automatically awaits each value.
Paginated API Iteration
async function* fetchPages(baseUrl) { let nextUrl = baseUrl; while (nextUrl) { const response = await fetch(nextUrl); const data = await response.json(); for (const item of data.results) { yield item; } nextUrl = data.next; } } async function getAllUsers() { const users = []; for await (const user of fetchPages('/api/users?page=1')) { users.push(user); if (users.length >= 100) break; } return users; }
The async generator handles pagination internally. The consumer can break early without unnecessary fetches.
Custom Async Iterable
class Timer { constructor(intervalMs, count) { this.intervalMs = intervalMs this.count = count } async *[Symbol.asyncIterator]() { for (let i = 0; i < this.count; i++) { await new Promise(r => setTimeout(r, this.intervalMs)) yield { tick: i + 1, time: Date.now() } } } } for await (const event of new Timer(1000, 5)) { console.log(`Tick ${event.tick}`) }
Objects with Symbol.asyncIterator become async iterables, consumable with for-await-of.
Cancellable Async Iteration
async function* fetchWithAbort(urls, signal) { for (const url of urls) { if (signal.aborted) return; const response = await fetch(url, { signal }); yield await response.json(); } } const controller = new AbortController(); setTimeout(() => controller.abort(), 5000); try { for await (const data of fetchWithAbort(urls, controller.signal)) { console.log('Got:', data); } } catch (err) { if (err.name === 'AbortError') console.log('Cancelled'); }
AbortController integrates with async iteration for clean cancellation.
Common Mistakes
- Using for-of instead of for-await-of with async iterables
- Forgetting to implement return() for cleanup when consumers break early
- Not handling errors inside async generators
- Creating unbounded queues without backpressure
Interview Tips
- next() returns Promise<IteratorResult> (vs plain IteratorResult for sync)
- Paginated API pattern is the most practical real-world use case
- for-await-of calls return() on break, enabling resource cleanup
- Node.js readable streams implement Symbol.asyncIterator natively