Iterators & Generators
ES6 introduced Iterators and Generators, providing a uniform interface for traversing data structures and a mechanism for writing pauseable, stateful functions.
The Iteration Protocols
JavaScript defines iteration capabilities through two formal protocols:
- The Iterable Protocol: An object is iterable if it defines a method at key
[Symbol.iterator]that returns an Iterator object. Built-in iterables includeArray,String,Map,Set, andNodeList. - The Iterator Protocol: An object that implements a
.next()method returning an object with two properties:value: The current item value.done: A boolean (truewhen iteration finishes).
Iteration Protocol Flow
Iterable Object ──[Symbol.iterator]()──> Iterator Object
│
.next()
│
▼
{ value: X, done: false }
Creating a Custom Iterator
const customSequence = {
start: 1,
end: 3,
[Symbol.iterator]() {
let current = this.start;
const last = this.end;
return {
next() {
if (current <= last) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
};
for (const num of customSequence) {
console.log(num); // Logs: 1, 2, 3
}
Generator Functions (function*)
Generators are special functions that can be paused mid-execution using the yield keyword and resumed on demand. Defining a function with function* returns a Generator Object, which implements both the Iterator and Iterable protocols.
function* idGenerator() {
let id = 1;
while (true) {
yield `USER_${id++}`;
}
}
const gen = idGenerator();
console.log(gen.next().value); // "USER_1"
console.log(gen.next().value); // "USER_2"
console.log(gen.next().value); // "USER_3"
Two-Way Generator Communication
The yield expression not only yields values out of the generator—it also receives values passed back into .next(value) when execution resumes:
function* conversation() {
const name = yield "What is your name?";
const action = yield `Hello, ${name}! What do you want to do?`;
return `Executing ${action} for ${name}.`;
}
const chat = conversation();
// 1. First next() starts execution until first yield
console.log(chat.next().value); // "What is your name?"
// 2. Pass value into first yield
console.log(chat.next("Alex").value); // "Hello, Alex! What do you want to do?"
// 3. Pass value into second yield
console.log(chat.next("Data Backup").value); // "Executing Data Backup for Alex."
Generator Delegation (yield*)
Use yield* to delegate iteration authority to another iterable or nested generator:
function* taskGroupA() {
yield "Task A1";
yield "Task A2";
}
function* masterWorkflow() {
yield "Workflow Started";
yield* taskGroupA(); // Delegates execution to taskGroupA
yield "Workflow Completed";
}
const workflow = masterWorkflow();
console.log([...workflow]);
// ["Workflow Started", "Task A1", "Task A2", "Workflow Completed"]
Asynchronous Generators (for await...of)
Async generators combine async/await with generators, yielding Promises that can be traversed sequentially using for await...of:
async function* fetchPaginatedPages(maxPages) {
for (let page = 1; page <= maxPages; page++) {
const data = await fetch(`https://api.example.com/items?page=${page}`).then(res => res.json());
yield data;
}
}
async function processData() {
for await (const pageData of fetchPaginatedPages(3)) {
console.log("Fetched Page Data:", pageData);
}
}
Interactive Playground: Fibonacci Generator
Observe infinite generator state evaluation without memory stack overflow:
Best Practices
- Use Generators for Memory-Efficient Streams: Generate large sequences lazily on demand instead of allocating massive arrays in RAM.
- Avoid Generator Overuse for Simple Loops: Use standard
for...ofloops or Array iterator methods (map,filter) when standard iteration suffices. - Always Consume Iterators Cleanly: Leverage
for...of,[...iterable], or Array destructuring to automatically consume iterator values untildone: true.
Knowledge Check
Exercise Requirements:
Write a custom iterable object range(start, end, step) using Symbol.iterator or a generator function that yields numbers from start to end incremented by step.
function* range(start, end, step = 1) {
let current = start;
while (current <= end) {
yield current;
current += step;
}
}
// Usage
console.log([...range(2, 10, 2)]); // [2, 4, 6, 8, 10]
Congratulations! You have completed Phase 06: Modern ES6+ Features. Proceed to Phase 07: Advanced JS Concepts!