Generators and Iterators
Understanding generators, iterables, and custom iteration protocols
Challenge
Section titled “Challenge”You need to create a function that yields values one at a time on demand instead of returning all at once. How can you create an infinite sequence that’s memory efficient?
Explanation
Section titled “Explanation”Generators are functions that can be paused and resumed, yielding values one at a time. They implement the iterator protocol, enabling lazy evaluation and memory-efficient iteration.
Generator Basics
Section titled “Generator Basics”function* countUp() { console.log('Starting'); yield 1; console.log('After 1'); yield 2; console.log('After 2'); yield 3;}
const gen = countUp();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 iterablefor (const value of countUp()) { console.log(value); // 1, 2, 3}Solution: Infinite Sequence Generator
Section titled “Solution: Infinite Sequence Generator”function* infiniteNumbers() { let n = 0; while (true) { yield n++; }}
// Memory efficient - doesn't create entire arrayconst numGen = infiniteNumbers();console.log(numGen.next().value); // 0console.log(numGen.next().value); // 1console.log(numGen.next().value); // 2
// Get first n numbersfunction* take(iterable, n) { let count = 0; for (const value of iterable) { if (count++ >= n) break; yield value; }}
console.log([...take(infiniteNumbers(), 5)]); // [0, 1, 2, 3, 4]
// Fibonacci sequencefunction* fibonacci() { let [a, b] = [0, 1]; while (true) { yield a; [a, b] = [b, a + b]; }}
console.log([...take(fibonacci(), 10)]); // First 10 Fibonacci numbersIterators and Iterables
Section titled “Iterators and Iterables”// Iterable: Has Symbol.iterator method// Iterator: Has next() method that returns { value, done }
// String is iterableconst str = 'hello';for (const char of str) { console.log(char); // h, e, l, l, o}
// Creating custom iterableconst range = { start: 1, end: 5,
[Symbol.iterator]() { let current = this.start; const end = this.end; return { next() { if (current <= end) { return { value: current++, done: false }; } return { done: true }; } }; }};
for (const num of range) { console.log(num); // 1, 2, 3, 4, 5}Generator Input/Output
Section titled “Generator Input/Output”function* dialog() { const name = yield 'What is your name?'; console.log('Name:', name);
const age = yield 'What is your age?'; console.log('Age:', age);
yield `${name} is ${age} years old`;}
const gen = dialog();console.log(gen.next()); // { value: 'What is your name?', done: false }console.log(gen.next('Alice')); // Sends 'Alice' to previous yieldconsole.log(gen.next(30)); // Sends 30 to previous yieldconsole.log(gen.next()); // { value: '...', done: true }Generator Delegation
Section titled “Generator Delegation”function* inner() { yield 1; yield 2;}
function* outer() { yield 'a'; yield* inner(); // Delegate to another generator yield 'b';}
console.log([...outer()]); // ['a', 1, 2, 'b']
// Tree traversal examplefunction* traverse(node) { yield node.value;
if (node.left) yield* traverse(node.left); if (node.right) yield* traverse(node.right);}Practical Example: Lazy Data Loading
Section titled “Practical Example: Lazy Data Loading”function* lazyDataLoader(dataSource) { let page = 1;
while (true) { const data = fetchPage(page); if (data.length === 0) break;
for (const item of data) { yield item; }
page++; }}
// Simulate fetchingfunction fetchPage(page) { if (page > 3) return []; return Array.from({ length: 10 }, (_, i) => ({ id: (page - 1) * 10 + i, title: `Item ${i}` }));}
const loader = lazyDataLoader('api/items');
// Load data on demandconsole.log(loader.next()); // First itemconsole.log(loader.next()); // Second item// Data is fetched page by page only as neededError Handling in Generators
Section titled “Error Handling in Generators”function* errorHandling() { try { yield 1; throw new Error('Generator error'); yield 2; } catch (error) { console.log('Caught:', error.message); yield 3; }}
const gen = errorHandling();console.log(gen.next()); // { value: 1, done: false }console.log(gen.throw(new Error('External error')));// Caught: External error// { value: 3, done: false }vs Alternatives
Section titled “vs Alternatives”// Problem: Recursive array processingconst data = [1, [2, 3, [4, 5]]];
// ❌ Complex with nested loopsfunction flatWithLoops(arr) { const result = []; for (let item of arr) { if (Array.isArray(item)) { result.push(...flatWithLoops(item)); } else { result.push(item); } } return result;}
// ✅ Clean with generatorsfunction* flatWithGenerator(arr) { for (const item of arr) { if (Array.isArray(item)) { yield* flatWithGenerator(item); } else { yield item; } }}
console.log([...flatWithGenerator(data)]); // [1, 2, 3, 4, 5]Performance Note
Section titled “Performance Note”// Creating all values upfrontfunction getAllNumbers() { const result = []; for (let i = 0; i < 1000000; i++) { result.push(i); } return result;}
// Lazy generation - only creates values as neededfunction* getNumbersLazy() { for (let i = 0; i < 1000000; i++) { yield i; }}
// Memory efficient: only first 10 items createdconst first10 = [...take(getNumbersLazy(), 10)];