Repository navigation
Expand file tree
/
Copy path03-iterators-generators.js
More file actions
103 lines (92 loc) · 3.46 KB
/
Copy path03-iterators-generators.js
File metadata and controls
103 lines (92 loc) · 3.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/**
* 03 — ITERATORS AND GENERATORS
* -------------------------------
* An ITERATOR is any object with a `.next()` method that returns
* `{ value, done }`. An ITERABLE is any object with a `Symbol.iterator`
* method that returns an iterator — this is what `for...of` and spread
* rely on. A GENERATOR is a special function (`function*`) that makes
* writing iterators far easier: calling it returns an iterator automatically.
*/
// ── 1. The iterator protocol, built by hand ──────────────────────────────
function makeCounterIterator(max) {
let current = 0;
return {
next() {
if (current < max) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
},
};
}
const counter = makeCounterIterator(3);
console.log(counter.next()); // { value: 0, done: false }
console.log(counter.next()); // { value: 1, done: false }
console.log(counter.next()); // { value: 2, done: false }
console.log(counter.next()); // { value: undefined, done: true }
// ── 2. Making an object ITERABLE with Symbol.iterator ────────────────────
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
return current <= last
? { value: current++, done: false }
: { value: undefined, done: true };
},
};
},
};
// Because `range` implements Symbol.iterator, for...of and spread work on it:
for (const n of range) console.log(n); // 1 2 3 4 5
console.log([...range]); // [1, 2, 3, 4, 5]
// ── 3. Generators — the easy way to write the same thing ────────────────
function* rangeGenerator(from, to) {
for (let i = from; i <= to; i++) {
yield i; // pauses execution here, returns the value, resumes on next `.next()`
}
}
for (const n of rangeGenerator(1, 5)) console.log(n); // 1 2 3 4 5
const gen = rangeGenerator(1, 3);
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 }
// ── 4. Generators can receive values back through .next(arg) ────────────
function* conversation() {
const name = yield "What's your name?";
const age = yield `Hi ${name}, how old are you?`;
return `${name} is ${age} years old.`;
}
const convo = conversation();
console.log(convo.next().value); // "What's your name?"
console.log(convo.next("Abdul").value); // "Hi Abdul, how old are you?"
console.log(convo.next(21).value); // "Abdul is 21 years old."
// ── 5. yield* — delegating to another generator/iterable ────────────────
function* inner() {
yield "a";
yield "b";
}
function* outer() {
yield 1;
yield* inner(); // delegates — yields each value from `inner()` in turn
yield 2;
}
console.log([...outer()]); // [1, 'a', 'b', 2]
// ── 6. Infinite generators — only possible because generators are LAZY ──
function* naturalNumbers() {
let n = 1;
while (true) yield n++;
}
function take(iterable, count) {
const result = [];
for (const value of iterable) {
if (result.length >= count) break;
result.push(value);
}
return result;
}
console.log(take(naturalNumbers(), 5)); // [1, 2, 3, 4, 5] — never actually runs forever