Async Generators and Practical Patterns
An async generator combines a generator with async/await. It produces its values asynchronously, and the caller reads them with for await...of.
Async Generators
We write an async generator with async function*. We can await inside it, and the caller iterates it with for await...of:
async function* asyncRange(start, end) {
for (let i = start; i <= end; i++) {
// Simulate async operation
await new Promise((resolve) => setTimeout(resolve, 100));
yield i;
}
}
// Use for-await-of to iterate
async function run() {
for await (const num of asyncRange(1, 5)) {
console.log(num); // Logs 1, 2, 3, 4, 5 with 100ms delays
}
}
run();
Async Data Stream Example
async function* fetchPages(baseUrl, maxPages) {
for (let page = 1; page <= maxPages; page++) {
const response = await fetch(`${baseUrl}?page=${page}`);
const data = await response.json();
if (data.items.length === 0) return;
yield data.items;
}
}
async function getAllItems() {
const allItems = [];
for await (const items of fetchPages("/api/data", 10)) {
allItems.push(...items);
}
return allItems;
}
Practical Use Cases
Tree Traversal
function* walkTree(node) {
yield node.value;
for (const child of node.children || []) {
yield* walkTree(child);
}
}
const tree = {
value: "root",
children: [
{ value: "a", children: [{ value: "a1" }, { value: "a2" }] },
{ value: "b" },
],
};
console.log([...walkTree(tree)]); // ['root', 'a', 'a1', 'a2', 'b']
ID Generator
function* idGenerator(prefix = "id") {
let id = 1;
while (true) {
yield `${prefix}_${id++}`;
}
}
const userIds = idGenerator("user");
console.log(userIds.next().value); // "user_1"
console.log(userIds.next().value); // "user_2"
Lazy Evaluation Pipeline
function* range(start, end, step = 1) {
for (let i = start; i <= end; i += step) {
yield i;
}
}
function* map(iterable, fn) {
for (const item of iterable) {
yield fn(item);
}
}
function* filter(iterable, predicate) {
for (const item of iterable) {
if (predicate(item)) yield item;
}
}
function* take(iterable, n) {
let count = 0;
for (const item of iterable) {
if (count++ >= n) return;
yield item;
}
}
// Lazily process a large range
const result = filter(
map(range(1, 1000000), (x) => x * 2),
(x) => x % 10 === 0,
);
// Only compute what we need
console.log([...take(result, 5)]); // [10, 20, 30, 40, 50]
Summary
| Concept | Description |
|---|---|
| Iterable | Object with [Symbol.iterator]() method |
| Iterator | Object with next() returning { value, done } |
| Generator | Function that can pause (yield) and resume |
yield* |
Delegate to another iterator/generator |
| Async Generator | Generator with async function* and for await...of |