0% found this document useful (0 votes)
5 views14 pages

Chapter 9 Iterators Generators

Chapter 9 discusses the iteration protocol in JavaScript, which allows objects to be iterable through methods like for...of and spread syntax. It covers the implementation of custom iterables, the use of generator functions for lazy evaluation, and practical applications such as unique ID generation and flattening nested structures. Additionally, it highlights built-in iterable collections like Set and Map, emphasizing their unique features and methods.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views14 pages

Chapter 9 Iterators Generators

Chapter 9 discusses the iteration protocol in JavaScript, which allows objects to be iterable through methods like for...of and spread syntax. It covers the implementation of custom iterables, the use of generator functions for lazy evaluation, and practical applications such as unique ID generation and flattening nested structures. Additionally, it highlights built-in iterable collections like Set and Map, emphasizing their unique features and methods.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

Chapter 9: Iterators, Generators, and the Iteration

Protocol
JavaScript has a built-in system for making any object iterable — meaning it can be
looped over with for...of, spread with ..., or destructured. This system is called the
iteration protocol. Understanding it reveals why arrays, strings, Sets, and Maps all
work with for...of, and how you can make your own custom objects work the same
way. Generators are a special kind of function built on top of this protocol that can
pause and resume execution, enabling powerful patterns for lazy sequences, infinite
data streams, and asynchronous control flow.

1. The Iteration Protocol


The iteration protocol is a set of rules that any object can implement to make itself
iterable. It consists of two related sub-protocols that must both be satisfied.

Protocol What It Requires Where It Lives

Iterable protocol The object must have a method at the key On the object
[Link]. This method must return an itself (or its
iterator object. prototype)

Iterator protocol The iterator object returned by On the iterator


[Link] must have a next() method. object returned
Each call to next() must return a result by
object with two properties: value (the current [Link]
value) and done (a boolean — false if more
values remain, true when finished).

KEY POINT: An iterable is an object that knows how to produce an iterator. An


iterator is the object that actually does the step-by-step traversal. These are two
separate roles but are often combined in the same object. Built-in iterables in
JavaScript include: Array, String, Set, Map, NodeList, arguments, TypedArray, and
generator objects.
2. How for...of Uses the Iteration Protocol
When you write for (const item of someIterable), JavaScript performs the following
steps automatically under the hood.

St
e What JavaScript Does Internally
p

1 Calls someIterable[[Link]]() to get the iterator object.

2 Calls [Link]() to get the first result: { value: ..., done: false }.

3 Assigns the value to the loop variable and runs the loop body.

4 Calls [Link]() again for the next iteration.

5 Repeats until next() returns { value: undefined, done: true }, then stops.

// Manually doing what for...of does automatically


const arr = ['a', 'b', 'c'];

const iterator = arr[[Link]](); // step 1 — get the iterator

[Link]([Link]()); // { value: 'a', done: false }


[Link]([Link]()); // { value: 'b', done: false }
[Link]([Link]()); // { value: 'c', done: false }
[Link]([Link]()); // { value: undefined, done: true }

3. Built-in Iterables
All of the following types implement the iterable protocol natively and therefore work
with for...of, spread (...), destructuring, [Link](), and any other construct that
consumes iterables.

Type What Each Iteration Produces

Array Each element in order.

String Each Unicode character (code point) one at a time.

Set Each unique value in insertion order.

Map Each [key, value] pair as a two-element array, in insertion


order.
Type What Each Iteration Produces

NodeList Each DOM element in the list.

arguments Each argument passed to the function.

Generator Each yielded value, one at a time, pausing between each.

// String — iterates over characters


for (const char of 'hello') {
[Link](char); // h, e, l, l, o
}

// Set — iterates over unique values


const set = new Set([1, 2, 2, 3]);
for (const val of set) {
[Link](val); // 1, 2, 3
}

// Map — iterates over [key, value] pairs


const map = new Map([['name', 'Joseph'], ['age', 21]]);
for (const [key, value] of map) {
[Link](key, ':', value);
}
// name : Joseph
// age : 21

4. Creating a Custom Iterable


You can make any object iterable by implementing the [Link] method on it.
This method must return an object with a next() method that follows the iterator
protocol.

// A custom range object that iterates from 'from' to 'to'


const range = {
from: 1,
to: 5,

// Implement [Link] to make this object iterable


[[Link]]() {
let current = [Link];
const last = [Link];

// Return the iterator object


return {
next() {
if (current <= last) {
return { value: current++, done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
};

// Now range works with for...of


for (const num of range) {
[Link](num); // 1, 2, 3, 4, 5
}

// And with spread


[Link]([...range]); // [1, 2, 3, 4, 5]

// And with destructuring


const [first, second] = range;
[Link](first, second); // 1 2

5. Generator Functions
A generator function is a special function that can pause its execution midway and
resume from exactly where it left off on the next call. It is defined using the function*
syntax (note the asterisk). When called, it does not execute its body immediately —
instead it returns a generator object, which is both an iterator and an iterable.

The yield keyword is used inside a generator to pause execution and produce a
value. Each time next() is called on the generator object, execution resumes from the
line after the last yield until the next yield or the end of the function.

a. Basic Generator
function* simpleGen() {
[Link]('Step 1');
yield 10; // pause here and return 10
[Link]('Step 2');
yield 20; // pause here and return 20
[Link]('Step 3');
return 30; // ends the generator — done: true
}

const gen = simpleGen(); // does NOT run the body yet

[Link]([Link]()); // Step 1 { value: 10, done: false }


[Link]([Link]()); // Step 2 { value: 20, done: false }
[Link]([Link]()); // Step 3 { value: 30, done: true }
[Link]([Link]()); // { value: undefined, done: true }

NOTE — return vs yield in a generator: A yield produces a value and pauses with
done: false — the generator can continue. A return ends the generator
permanently with done: true. Any next() call after a return always produces { value:
undefined, done: true }.

b. Generator as an Iterable
Because a generator object implements both the iterator and iterable protocols, it
works directly with for...of, spread, and destructuring — just like arrays and strings.

function* colours() {
yield 'red';
yield 'green';
yield 'blue';
}

// for...of — consumes the generator


for (const colour of colours()) {
[Link](colour); // red, green, blue
}

// Spread — collects all yielded values into an array


[Link]([...colours()]); // ['red', 'green', 'blue']

// Destructuring
const [first, , third] = colours();
[Link](first, third); // red blue

c. Infinite Generators
A generator can yield values indefinitely without exhausting memory because values
are produced lazily — only when next() is called. This makes generators ideal for
infinite sequences such as IDs, counters, or data streams.

function* idGenerator() {
let id = 1;
while (true) { // infinite loop — safe inside a generator
yield id++;
}
}

const nextId = idGenerator();

[Link]([Link]().value); // 1
[Link]([Link]().value); // 2
[Link]([Link]().value); // 3
// The generator pauses after each yield — memory is not exhausted

d. Passing Values into a Generator with next()


You can send a value back into a running generator by passing an argument to
next(). The value becomes the result of the yield expression inside the generator —
meaning the paused yield statement evaluates to whatever was passed in.

function* conversation() {
const name = yield 'What is your name?'; // pauses, receives
name
const age = yield `Hello ${name}! How old are you?`; // pauses,
receives age
yield `Nice to meet you, ${name}. ${age} is a great age!`;
}

const chat = conversation();

[Link]([Link]().value); // 'What is your name?'


[Link]([Link]('Joseph').value); // 'Hello Joseph! How old
are you?'
[Link]([Link](21).value); // 'Nice to meet you,
Joseph. 21 is a great age!'

NOTE — The first call to next() cannot pass a value in. The generator has not yet
reached a yield statement, so there is nowhere to receive it. The first next() simply
starts the generator running until it hits the first yield. Values passed to next() only
take effect from the second call onwards.

e. yield* — Delegating to Another Iterable


The yield* expression delegates iteration to another iterable or generator. It is
equivalent to looping over the inner iterable and yielding each of its values one by
one.

function* inner() {
yield 'a';
yield 'b';
}

function* outer() {
yield 1;
yield* inner(); // delegate — yields 'a' then 'b'
yield 2;
}

[Link]([...outer()]); // [1, 'a', 'b', 2]

// Works with any iterable — not just other generators


function* spreadArray() {
yield* [10, 20, 30]; // delegates to the array
yield* 'hi'; // delegates to the string
}
[Link]([...spreadArray()]); // [10, 20, 30, 'h', 'i']

6. Generator Object Methods


A generator object exposes three methods beyond next() for more advanced control.

Method What It Does

next(value) Resumes the generator. The optional value becomes the


result of the current yield expression inside the generator.

return(value) Terminates the generator immediately, as if the current yield


were replaced with a return statement. Returns { value: value,
done: true }.

throw(error) Resumes the generator and causes an error to be thrown at


the point where it is currently paused (at the yield). The
Method What It Does

generator can catch this with try...catch internally.

function* gen() {
try {
yield 1;
yield 2;
yield 3;
} catch (err) {
[Link]('Caught inside generator:', [Link]);
}
}

const g = gen();
[Link]([Link]()); // { value: 1, done: false }
[Link]([Link](new Error('oops'))); // Caught inside generator:
oops
// { value: undefined, done:
true }

// return() — early termination


const g2 = gen();
[Link]([Link]()); // { value: 1, done: false }
[Link]([Link](99)); // { value: 99, done: true }
[Link]([Link]()); // { value: undefined, done: true }

7. Practical Use Cases for Generators

a. Lazy Evaluation — Process Data One Item at a Time


Instead of loading an entire dataset into memory, a generator produces one item at a
time. This is especially useful when working with large files, database cursors, or
paginated API responses.

function* readLargeFile(lines) {
for (const line of lines) {
yield [Link](); // process and yield one line at a time
}
}

const lines = [' hello ', ' world ', ' foo '];
const reader = readLargeFile(lines);
for (const line of reader) {
[Link](line); // hello, world, foo
}

b. Unique ID Factory
function* createIdFactory(prefix = 'id') {
let count = 1;
while (true) {
yield `${prefix}-${count++}`;
}
}

const userId = createIdFactory('user');


const productId = createIdFactory('prod');

[Link]([Link]().value); // user-1
[Link]([Link]().value); // user-2
[Link]([Link]().value); // prod-1 — independent
sequence

c. Flattening Nested Structures


yield* makes it natural to recursively walk and flatten arbitrarily nested structures
without loading everything into memory first.

function* flatten(arr) {
for (const item of arr) {
if ([Link](item)) {
yield* flatten(item); // recurse into nested arrays
} else {
yield item;
}
}
}

const nested = [1, [2, [3, [4]], 5], 6];


[Link]([...flatten(nested)]); // [1, 2, 3, 4, 5, 6]

8. Set and Map — Built-in Iterable Collections


Set and Map are two built-in collection types introduced in ES6. They are fully
iterable and solve specific problems that plain arrays and objects cannot handle
cleanly.
a. Set
A Set stores unique values of any type. Duplicate values are silently ignored. Sets
maintain insertion order and are iterable.

const set = new Set();

// Adding values
[Link](1);
[Link](2);
[Link](2); // duplicate — silently ignored
[Link]('hello');

[Link](set); // Set(3) { 1, 2, 'hello' }


[Link]([Link]); // 3

// Checking, deleting
[Link]([Link](2)); // true
[Link](2);
[Link]([Link](2)); // false

// Iterating
for (const val of set) {
[Link](val); // 1, 'hello'
}

// Most common practical use — remove duplicates from an array


const nums = [1, 2, 2, 3, 3, 3, 4];
const unique = [...new Set(nums)];
[Link](unique); // [1, 2, 3, 4]

Method / Property Description

new Set(iterable) Creates a new Set. Optionally pass an iterable to pre-


populate it.

[Link](value) Adds a value. Returns the Set itself — chainable.

[Link](value) Removes a value. Returns true if the value existed.

[Link](value) Returns true if the value is in the Set.


Method / Property Description

[Link]() Removes all values.

[Link] Returns the number of unique values.

[Link](fn) Iterates over values, calling fn(value, value, set) for


each.

[Link]() Returns an iterator of values.

[Link]() Same as values() — exists for API parity with Map.

[Link]() Returns an iterator of [value, value] pairs.

b. Map
A Map stores key-value pairs where keys can be of any type — including objects,
functions, and primitives. Unlike a plain object (which only accepts string or Symbol
keys), a Map preserves insertion order and provides a clean API for working with
key-value data.

const map = new Map();

// Setting entries
[Link]('name', 'Joseph');
[Link]('age', 21);
[Link](42, 'the answer'); // number as key

const objKey = { id: 1 };


[Link](objKey, 'object as key'); // object as key

[Link]([Link]); // 4
[Link]([Link]('name')); // 'Joseph'
[Link]([Link](42)); // 'the answer'
[Link]([Link]('age')); // true

// Iterating — produces [key, value] pairs


for (const [key, value] of map) {
[Link](key, '→', value);
}

// Convert to array of entries


[Link]([...[Link]()]);

// Convert to plain object (only safe if all keys are strings/symbols)


const obj = [Link](map);

Method / Property Description

new Map(iterable) Creates a Map. Optionally pass an iterable of [key,


value] pairs.

[Link](key, value) Adds or updates a key-value pair. Returns the Map —


chainable.

[Link](key) Returns the value associated with the key, or undefined.

[Link](key) Returns true if the key exists.

[Link](key) Removes the entry for a key. Returns true if it existed.

[Link]() Removes all entries.

[Link] Returns the number of key-value pairs.

[Link]() Returns an iterator of keys.

[Link]() Returns an iterator of values.

[Link]() Returns an iterator of [key, value] pairs — same as


for...of.

[Link](fn) Calls fn(value, key, map) for each entry.

Set vs Array and Map vs Object — When to Use Which

Use Case Best Choice Reason

Ordered collection that Array Arrays preserve order and allow


may have duplicates duplicate values.

Collection of unique Set Set automatically discards duplicates


values without a manual check.

Remove duplicates Set + spread [...new Set(array)] is the cleanest


from an array one-liner.

Key-value store with Plain Object { } Simpler syntax, works with


string keys, JSON- [Link].
friendly
Use Case Best Choice Reason

Key-value store with Map Map accepts any type as a key —


non-string keys objects, numbers, functions.

Need to know the size Map or Set Both have a .size property. Objects
easily require [Link]().length.

Frequent addition and Map Map is optimised for this. Object


removal of entries property deletion is slower.

Preserving insertion Map Map guarantees insertion order.


order of keys Plain objects mostly do but with
exceptions.

9. Quick Reference

Concept Key Point

Iterable protocol Object must have [[Link]]() that returns an


iterator.

Iterator protocol Object must have next() returning { value, done }.

for...of Works on any iterable — calls [Link]


automatically.

Built-in iterables Array, String, Set, Map, NodeList, arguments, generators.

Generator function Declared with function*. Returns a generator object when


called.

yield Pauses the generator and produces a value. done: false.

return (in generator) Ends the generator permanently. done: true.

yield* Delegates to another iterable — yields all its values in


sequence.

next(value) Resumes the generator. The argument becomes the


result of the yield.

[Link](v) Terminates the generator early with done: true.

[Link](err) Throws an error into the generator at the current yield


point.

Set Ordered collection of unique values.


Concept Key Point

Use .add(), .has(), .delete().

Map Ordered key-value collection. Any type as a key.


Use .set(), .get(), .has().

Lazy evaluation Generators produce values on demand — no need to


build the whole sequence upfront.

You might also like