/
generators-as-iterators.js
291 lines (232 loc) · 6.8 KB
/
generators-as-iterators.js
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
(function() {
'use strict';
function* range(start, count) {
for (let delta = 0; delta < count; delta++) {
yield start + delta;
}
}
function* basicGeneratorFunc() {
console.log('before yield');
yield;
console.log('after yield');
}
function* awesomeGeneratorFunc() {
console.log('start');
console.log('first yield');
yield 'Generators';
console.log('second yield');
yield 'are';
console.log('third yield');
yield 'awesome!';
console.log('all done!');
return 1000;
}
function* delegatedGeneratorFunc(start) {
// yield the first item in the generator
yield 'before';
// delegate yielding to `awesomeGeneratorFunc()` which will add
// 3 more items
yield* awesomeGeneratorFunc();
// yield 7th item
yield 'between';
// delegate yielding to `range()` which will add 5 items
// we can pass parameters/variables just like regular functions
// without `yield*` we'd just get back a new range generator
// with only `yield`, the generator would be added as 10th item
yield* range(start, 5);
// yield 11th and final item
yield 'after';
}
function* iterableGeneratorFunc() {
yield 'adios';
yield* 'hello'; // a string is an iterable!
yield 'au revoir';
}
function* delegatedGeneratorFuncV2() {
// we're still including the 3 items yielded by awesomeGeneratorFunc(),
// but we're also saving the return value in a variable
let start = yield* awesomeGeneratorFunc();
// we can now use that variable to initialize range()
yield* range(start, 3);
}
for (let teenageYear of range(13, 7)) {
console.log(`Teenage angst @ ${teenageYear}!`);
}
let basicGenerator = basicGeneratorFunc();
// nothing has happened yet, just have a generator
// output:
// before yield
// {value: undefined, done: false}
console.log(basicGenerator.next());
// this will be executed before 'after yield'
// is written to the log
console.log('after first next');
// Output:
// after yield
// {value: undefined, done: true}
console.log(basicGenerator.next());
// additional calls to .next() do nothing
// Output:
// {value: undefined, done: false}
console.log(basicGenerator.next());
console.log('===== MANUAL CONSUMPTION =====');
let awesomeGeneratorObj = awesomeGeneratorFunc();
// output:
// start
// first yield
// {value: 'Generators', done: false}
console.log(awesomeGeneratorObj.next());
// output:
// second yield
// {value: 'are', done: false}
console.log(awesomeGeneratorObj.next());
// output:
// third yield
// {value: 'awesome!', done: false}
console.log(awesomeGeneratorObj.next());
// output:
// all done!
// {value: 1000, done: true}
console.log(awesomeGeneratorObj.next());
// output:
// {value: undefined, done: true}
console.log(awesomeGeneratorObj.next());
// output:
// {value: undefined, done: true}
console.log(awesomeGeneratorObj.next());
console.log('===== FOR-OF CONSUMPTION =====');
// output:
// start
// first yield
// Generators
// second yield
// are
// third yield
// awesome!
// all done!
for (let word of awesomeGeneratorFunc()) {
console.log(`value: "${word}"`);
}
console.log('===== DESTRUCTURING CONSUMPTION =====');
// output:
// start
// first yield
// second yield
let [firstValue, secondValue] = awesomeGeneratorFunc();
// output: 'Generators'
console.log(firstValue);
// output: 'are'
console.log(secondValue);
console.log('===== SPREAD OPERATOR CONSUMPTION =====');
let generatedArray = [...awesomeGeneratorFunc()];
// output:
// start
// first yield
// second yield
// third yield
// all done!
// ['Generators', 'are', 'awesome!']
console.log(generatedArray);
// quickly see contents of generator by converting to an array
// output:
// ['before', 1, 2, 3, 4, 5, 'between', 'Generators', 'area', 'awesome', 'after']
console.log([...delegatedGeneratorFunc(1)]);
// quickly see contents of generator by converting to an array
// output: ['adios', 'H', 'e', 'l', 'l', 'o', 'au revoir']
console.log([...iterableGeneratorFunc()]);
// output: ['Generators', 'are', 'awesome', 1000, 1001, 1002]
console.log([...delegatedGeneratorFuncV2()]);
class BinaryTree {
constructor(value, left, right) {
this.value = value;
this.left = left;
this.right = right;
}
// default `@@iterator` is a generator function so
// it needs the `*`
*[Symbol.iterator]() {
if (this.left) {
yield* this.left;
}
// Let's do infix/in-order iteration
yield this.value;
if (this.right) {
yield* this.right;
}
}
}
let tree = new BinaryTree(4,
new BinaryTree(2,
new BinaryTree(1),
new BinaryTree(3)),
new BinaryTree(5));
// output: [1, 2, 3, 4, 5]
console.log([...tree]);
// Enumerable class that wraps an iterator exposing methods
// to lazily transform the items
class Enumerable {
constructor(iterator) {
this._iterator = iterator;
}
*[Symbol.iterator]() {
yield* this._iterator;
}
// Static (and private) helper generator functions
static *_filter(iterator, predicate) {
for (let value of iterator) {
if (predicate(value)) {
yield value;
}
}
}
static *_map(iterator, mapperFunc) {
for (let value of iterator) {
yield mapperFunc(value);
}
}
static *_take(iterator, count) {
let index = -1;
for (let value of iterator) {
if (++index >= count) {
break;
}
yield value;
}
}
// Instance methods wrapping functional helpers which allow for chaining
// They essentially act as iterator transformers
filter(predicate) {
this._iterator = Enumerable._filter(this._iterator, predicate);
return this;
}
map(mapper) {
this._iterator = Enumerable._map(this._iterator, mapper);
return this;
}
take(count) {
this._iterator = Enumerable._take(this._iterator, count);
return this;
}
}
function generateStocks() {
// Returns an infinite generator that keeps on returning new stocks
function* _generate() {
for (let stockNo = 1; ; stockNo++) {
let stockInfo = {
name: `Stock #${stockNo}`,
price: +(Math.random() * 100).toFixed(2)
};
console.log('Generated stock info', stockInfo);
yield stockInfo;
}
}
return new Enumerable(_generate());
}
let enumerable = generateStocks()
.filter(stockInfo => stockInfo.price > 30)
.map(stockInfo => `${stockInfo.name} ($${stockInfo.price})`)
.take(5);
// Even though `_generate()` is an infinite generator, it's also lazy so
// we only look at enough stocks that are > 30 until we get 5 of them
console.log([...enumerable]);
}) ();