Introduction
Async generators combine generators with promises, letting you yield async values. Combined with for await...of, they enable elegant streaming and pagination patterns that would be complex with promises alone.
Key Concepts
async function*: A generator that can use await and yields promises.
for await...of: Loop that awaits each iteration.
Async Iterable: Object with [Symbol.asyncIterator].
Deep Dive
Async Generator Syntax
javascriptasync function* fetchPages(url) { let page = 1; while (true) { const response = await fetch(`${url}?page=${page}`); const data = await response.json(); if (data.length === 0) return; // No more pages yield data; // Yield the page data page++; } } // Consume with for await...of async function processAllPages() { for await (const pageData of fetchPages('/api/items')) { console.log('Got page:', pageData.length, 'items'); } }
for await...of Loop
javascript// Works with async iterables const asyncIterable = { [Symbol.asyncIterator]() { let i = 0; return { async next() { if (i < 3) { await new Promise(r => setTimeout(r, 100)); return { value: i++, done: false }; } return { done: true }; } }; } }; for await (const num of asyncIterable) { console.log(num); // 0, 1, 2 (with 100ms delays) }
Practical Streaming Example
javascriptasync function* readChunks(stream) { const reader = stream.getReader(); try { while (true) { const { done, value } = await reader.read(); if (done) return; yield value; } } finally { reader.releaseLock(); } } // Usage const response = await fetch('/large-file'); for await (const chunk of readChunks(response.body)) { processChunk(chunk); }
Converting Callbacks to Async Iterator
javascriptasync function* socketMessages(socket) { const messages = []; let resolve; socket.on('message', (msg) => { messages.push(msg); resolve?.(); }); socket.on('close', () => resolve?.()); while (socket.connected) { if (messages.length === 0) { await new Promise(r => resolve = r); } while (messages.length) { yield messages.shift(); } } }
Array.fromAsync() (ES2024+)
When you want to collect all values from an async iterable into an array:
javascript// Before: manual collection const results = []; for await (const item of asyncIterable) { results.push(item); } // After: one-liner with Array.fromAsync() const results = await Array.fromAsync(asyncIterable); // Also works with async mapping const doubled = await Array.fromAsync( fetchPages('/api/items'), async (page) => page.map(item => item.id) );
Array.fromAsync() is the async equivalent of Array.from(). It awaits each value from the async iterable and collects them into a plain array.
Common Pitfalls
- Using for...of instead of for await...of: Won't await promises.
- Forgetting error handling: Wrap in try/catch or handle in consumer.
- Memory buildup: Don't buffer too much data.
Best Practices
- Use for await...of for async sequences: Cleaner than manual iteration.
- Handle errors in the loop: Each iteration can fail.
- Close resources in finally: Readers, connections, etc.
Summary
Async generators (async function*) combine await and yield. for await...of consumes async iterables. Great for pagination, streaming, and real-time data. Always handle cleanup in finally blocks.
Code Examples
async function* fetchPages(url) {
let page = 1;
while (true) {
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
if (data.length === 0) return; // No more pages
yield data; // Yield the page data
page++;
}
}
// Consume with for await...of
async function processAllPages() {
for await (const pageData of fetchPages('/api/items')) {
console.log('Got page:', pageData.length, 'items');
}
}// Works with async iterables
const asyncIterable = {
[Symbol.asyncIterator]() {
let i = 0;
return {
async next() {
if (i < 3) {
await new Promise(r => setTimeout(r, 100));
return { value: i++, done: false };
}
return { done: true };
}
};
}
};
for await (const num of asyncIterable) {
console.log(num); // 0, 1, 2 (with 100ms delays)
}