Generators, Advanced Iteration
Generators
Generators are functions that can be paused and resumed, producing multiple values on demand.
Generator Function Syntax
A generator is defined with function* and uses yield to produce values:
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}
let gen = numberGenerator();
console.log(gen.next()); // โ { value: 1, done: false }
console.log(gen.next()); // โ { value: 2, done: false }
console.log(gen.next()); // โ { value: 3, done: false }
console.log(gen.next()); // โ { value: undefined, done: true }
Generators are Iterable
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}
for (let value of numberGenerator()) {
console.log(value); // โ 1, 2, 3
}
console.log([...numberGenerator()]); // โ [1, 2, 3]
Infinite Generators
function* infiniteSequence() {
let i = 0;
while (true) {
yield i++;
}
}
let gen = infiniteSequence();
console.log(gen.next().value); // โ 0
console.log(gen.next().value); // โ 1
console.log(gen.next().value); // โ 2
// ... keeps going forever (but lazily!)
Generator with Arguments
function* range(start, end, step = 1) {
for (let i = start; i <= end; i += step) {
yield i;
}
}
console.log([...range(1, 10, 2)]); // โ [1, 3, 5, 7, 9]
console.log([...range(5, 1, -1)]); // โ [5, 4, 3, 2, 1]
Two-way Passing with yield
You can pass values back into a generator:
function* questionGenerator() {
let name = yield "What is your name?";
let age = yield `Hello, ${name}! How old are you?`;
return `${name} is ${age} years old.`;
}
let gen = questionGenerator();
console.log(gen.next().value); // โ "What is your name?"
console.log(gen.next("Alice").value); // โ "Hello, Alice! How old are you?"
console.log(gen.next(30).value); // โ "Alice is 30 years old."
Generator.return() and Generator.throw()
function* gen() {
try {
yield "One";
yield "Two";
yield "Three";
} catch (err) {
console.log("Caught:", err.message);
}
}
let g = gen();
console.log(g.next()); // โ { value: "One", done: false }
// Return early
console.log(g.return("Early exit")); // โ { value: "Early exit", done: true }
// Throw into generator
g = gen();
console.log(g.next()); // โ { value: "One", done: false }
g.throw(new Error("Boom!")); // โ "Caught: Boom!"
yield* โ Delegating to Another Generator
function* numbers() {
yield 1;
yield 2;
}
function* letters() {
yield "a";
yield "b";
}
function* combined() {
yield* numbers();
yield* letters();
yield* [true, false]; // Works with any iterable
}
console.log([...combined()]); // โ [1, 2, "a", "b", true, false]
// Without yield*, you'd have to manually iterate:
function* combinedManual() {
for (let v of numbers()) yield v;
for (let v of letters()) yield v;
}
Practical Generator Examples
// Lazy Fibonacci sequence
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
let fib = fibonacci();
for (let i = 0; i < 10; i++) {
console.log(fib.next().value);
}
// โ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
// Pagination generator
function* paginate(items, pageSize) {
for (let i = 0; i < items.length; i += pageSize) {
yield items.slice(i, i + pageSize);
}
}
let items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (let page of paginate(items, 3)) {
console.log(page); // โ [1,2,3], [4,5,6], [7,8,9], [10]
}
State Machine with Generators
function* trafficLight() {
while (true) {
yield "Green";
yield "Yellow";
yield "Red";
}
}
let light = trafficLight();
console.log(light.next().value); // โ "Green"
console.log(light.next().value); // โ "Yellow"
console.log(light.next().value); // โ "Red"
console.log(light.next().value); // โ "Green" (cycles)
Demo: Generators Demo
JavaScript
// Custom range generator
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
// Use it
let squares = [];
for (let n of range(1, 10)) {
squares.push(n * n);
}
document.write('Squares 1-10: ' + squares.join(', ') + '<br>');
// Fibonacci
function* fibonacci(n) {
let a = 0, b = 1;
for (let i = 0; i < n; i++) {
yield a;
[a, b] = [b, a + b];
}
}
document.write('Fibonacci: ' + [...fibonacci(10)].join(', ') + '<br>');
// Even numbers with generator
function* evens(limit) {
for (let i = 0; i <= limit; i += 2) {
yield i;
}
}
document.write('Evens to 20: ' + [...evens(20)].join(', ') + '<br>'); Live Output Window
Async Iteration and Generators
Async Iterables
Objects with Symbol.asyncIterator support for await...of:
let asyncRange = {
from: 1,
to: 5,
[Symbol.asyncIterator]() {
let current = this.from;
let end = this.to;
return {
next() {
if (current <= end) {
return new Promise(resolve => {
setTimeout(() => {
resolve({ value: current++, done: false });
}, 500);
});
}
return Promise.resolve({ done: true });
}
};
}
};
// Consume with for await...of
(async () => {
for await (let value of asyncRange) {
console.log(value); // โ 1, 2, 3, 4, 5 (each after 500ms)
}
})();
Async Generators
Combine async and generator for lazy async sequences:
async function* fetchPages(url, maxPages) {
for (let page = 1; page <= maxPages; page++) {
// Simulate API call
let response = await fetch(`${url}?page=${page}`);
let data = await response.json();
if (data.length === 0) break; // No more data
yield data;
}
}
(async () => {
for await (let page of fetchPages("/api/items", 10)) {
console.log("Got page:", page);
}
})();
Async Generator Example
// Simulated async data stream
async function* randomNumbers(count) {
for (let i = 0; i < count; i++) {
// Simulate async delay
let number = await new Promise(resolve =>
setTimeout(() => resolve(Math.random()), 200)
);
yield number;
}
}
(async () => {
let sum = 0;
let count = 0;
for await (let num of randomNumbers(5)) {
sum += num;
count++;
console.log(`Received: ${num.toFixed(3)}`);
}
console.log(`Average: ${(sum / count).toFixed(3)}`);
})();
Async Generator from Events
// Convert DOM events to async iterator
function fromEvent(element, eventName) {
return {
[Symbol.asyncIterator]() {
let resolvers = [];
element.addEventListener(eventName, (event) => {
let resolve = resolvers.shift();
if (resolve) resolve(event);
});
return {
next() {
return new Promise(resolve => {
resolvers.push(resolve);
});
}
};
}
};
}
// Usage (in browser):
// let clicks = fromEvent(document, "click");
// for await (let click of clicks) {
// console.log("Clicked at:", click.x, click.y);
// }
Async Iteration with Error Handling
async function* asyncData() {
let i = 0;
while (i < 3) {
try {
let value = await new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.7) reject(new Error("Random failure"));
else resolve(i++);
}, 500);
});
yield value;
} catch (err) {
console.log("Caught error, continuing:", err.message);
// Continue iteration despite errors
}
}
}
(async () => {
for await (let value of asyncData()) {
console.log("Got:", value);
}
})();