You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: 1-js/12-generators-iterators/1-generators/article.md
+55-63
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
Regular functions return only one, single value (or nothing).
4
4
5
-
Generators can return ("yield") multiple values, possibly an infinite number of values, one after another, on-demand. They work great with [iterables](info:iterable), allowing to create data streams with ease.
5
+
Generators can return ("yield") multiple values, one after another, on-demand. They work great with [iterables](info:iterable), allowing to create data streams with ease.
The term "generator function" is a bit misleading, because when called it does not execute the code. Instead, it returns a special object, called "generator object".
21
+
Generator functions behave differently from regular ones. When such function is called, it doesn't run its code. Instead it returns a special object, called "generator object", to manage the execution.
The `generator` object is something like an "frozen function call":
39
+
The function code execution hasn't started yet:
31
40
32
41

33
42
34
-
Upon creation, the code execution is paused at the very beginning.
43
+
The main method of a generator is `next()`. When called, it runs the execution till the nearest `yield <value>` statement (`value` can be omitted, then it's `undefined`). Then the function execution pauses, and the yielded `value`is returned to the outer code.
35
44
36
-
The main method of a generator is `next()`. When called, it resumes execution till the nearest `yield <value>` statement. Then the execution pauses, and the value is returned to the outer code.
45
+
The result of `next()` is always an object with two properties:
46
+
-`value`: the yielded value.
47
+
-`done`: `true` if the function code has finished, otherwise `false`.
37
48
38
49
For instance, here we create the generator and get its first yielded value:
Now the generator is done. We should see it from `done:true` and process `value:3` as the final result.
85
92
86
-
New calls `generator.next()` don't make sense any more. If we make them, they return the same object: `{done: true}`.
87
-
88
-
There's no way to "roll back" a generator. But we can create another one by calling `generateSequence()`.
89
-
90
-
So far, the most important thing to understand is that generator functions, unlike regular function, do not run the code. They serve as "generator factories". Running `function*` returns a generator, and then we ask it for values.
93
+
New calls `generator.next()` don't make sense any more. If we do them, they return the same object: `{done: true}`.
91
94
92
95
```smart header="`function* f(…)` or `function *f(…)`?"
93
-
That's a minor religious question, both syntaxes are correct.
96
+
Both syntaxes are correct.
94
97
95
98
But usually the first syntax is preferred, as the star `*` denotes that it's a generator function, it describes the kind, not the name, so it should stick with the `function` keyword.
96
99
```
@@ -115,11 +118,11 @@ for(let value of generator) {
115
118
}
116
119
```
117
120
118
-
That's a much better-looking way to work with generators than calling `.next().value`, right?
121
+
Looks a lot nicer than calling `.next().value`, right?
119
122
120
123
...But please note: the example above shows `1`, then `2`, and that's all. It doesn't show `3`!
121
124
122
-
It's because for-of iteration ignores the last `value`, when `done: true`. So, if we want all results to be shown by `for..of`, we must return them with `yield`:
125
+
It's because `for..of` iteration ignores the last `value`, when `done: true`. So, if we want all results to be shown by `for..of`, we must return them with `yield`:
123
126
124
127
```js run
125
128
function*generateSequence() {
@@ -137,7 +140,7 @@ for(let value of generator) {
137
140
}
138
141
```
139
142
140
-
Naturally, as generators are iterable, we can call all related functionality, e.g. the spread operator `...`:
143
+
As generators are iterable, we can call all related functionality, e.g. the spread operator `...`:
141
144
142
145
```js run
143
146
function*generateSequence() {
@@ -151,7 +154,7 @@ let sequence = [0, ...generateSequence()];
151
154
alert(sequence); // 0, 1, 2, 3
152
155
```
153
156
154
-
In the code above, `...generateSequence()` turns the iterable into array of items (read more about the spread operator in the chapter [](info:rest-parameters-spread-operator#spread-operator))
157
+
In the code above, `...generateSequence()` turns the iterable generator object into array of items (read more about the spread operator in the chapter [](info:rest-parameters-spread-operator#spread-operator))
155
158
156
159
## Using generators for iterables
157
160
@@ -185,28 +188,13 @@ let range = {
185
188
}
186
189
};
187
190
191
+
// iteration over range returns numbers from range.from to range.to
188
192
alert([...range]); // 1,2,3,4,5
189
193
```
190
194
191
-
Using a generator to make iterable sequences is simpler and much more elegant:
192
-
193
-
```js run
194
-
function*generateSequence(start, end) {
195
-
for (let i = start; i <= end; i++) {
196
-
yield i;
197
-
}
198
-
}
199
-
200
-
let sequence = [...generateSequence(1,5)];
201
-
202
-
alert(sequence); // 1, 2, 3, 4, 5
203
-
```
204
-
205
-
## Converting Symbol.iterator to generator
206
-
207
-
We can add generator-style iteration to any custom object by providing a generator as `Symbol.iterator`.
195
+
We can use a generator function for iteration by providing it as `Symbol.iterator`.
208
196
209
-
Here's the same `range`, but with a much more compact iterator:
197
+
Here's the same `range`, but much more compact:
210
198
211
199
```js run
212
200
let range = {
@@ -229,7 +217,7 @@ That works, because `range[Symbol.iterator]()` now returns a generator, and gene
229
217
230
218
That's not a coincidence, of course. Generators were added to JavaScript language with iterators in mind, to implement them easier.
231
219
232
-
The last variant with a generator is much more concise than the original iterable code of `range`, and keeps the same functionality.
220
+
The variant with a generator is much more concise than the original iterable code of `range`, and keeps the same functionality.
233
221
234
222
```smart header="Generators may generate values forever"
235
223
In the examples above we generated finite sequences, but we can also make a generator that yields values forever. For instance, an unending sequence of pseudo-random numbers.
@@ -241,18 +229,26 @@ That surely would require a `break` (or `return`) in `for..of` over such generat
241
229
242
230
Generator composition is a special feature of generators that allows to transparently "embed" generators in each other.
243
231
244
-
For instance, we'd like to generate a sequence of:
245
-
- digits `0..9` (character codes 48..57),
232
+
For instance, we have a function that generates a sequence of numbers:
233
+
234
+
```js
235
+
function*generateSequence(start, end) {
236
+
for (let i = start; i <= end; i++) yield i;
237
+
}
238
+
```
239
+
240
+
Now we'd like to reuse it for generation of a more complex sequence:
241
+
- first, digits `0..9` (with character codes 48..57),
246
242
- followed by alphabet letters `a..z` (character codes 65..90)
247
243
- followed by uppercased letters `A..Z` (character codes 97..122)
248
244
249
-
We can use the sequence e.g. to create passwords by selecting characters from it (could add syntax characters as well), but let's generate it first.
250
-
251
-
We already have `function* generateSequence(start, end)`. Let's reuse it to deliver 3 sequences one after another, together they are exactly what we need.
245
+
We can use this sequence e.g. to create passwords by selecting characters from it (could add syntax characters as well), but let's generate it first.
252
246
253
247
In a regular function, to combine results from multiple other functions, we call them, store the results, and then join at the end.
254
248
255
-
For generators, we can do better, like this:
249
+
For generators, there's a special `yield*` syntax to "embed" (compose) one generator into another.
250
+
251
+
The composed generator:
256
252
257
253
```js run
258
254
function*generateSequence(start, end) {
@@ -283,7 +279,7 @@ for(let code of generatePasswordCodes()) {
283
279
alert(str); // 0..9A..Za..z
284
280
```
285
281
286
-
The special `yield*` directive in the example is responsible for the composition. It *delegates* the execution to another generator. Or, to say it simple,`yield* gen` iterates over the generator `gen` and transparently forwards its yields outside. As if the values were yielded by the outer generator.
282
+
The `yield*` directive *delegates* the execution to another generator. This term means that`yield* gen` iterates over the generator `gen` and transparently forwards its yields outside. As if the values were yielded by the outer generator.
287
283
288
284
The result is the same as if we inlined the code from nested generators:
289
285
@@ -316,15 +312,11 @@ for(let code of generateAlphaNum()) {
316
312
alert(str); // 0..9A..Za..z
317
313
```
318
314
319
-
A generator composition is a natural way to insert a flow of one generator into another.
320
-
321
-
It works even if the flow of values from the nested generator is infinite. It's simple and doesn't use extra memory to store intermediate results.
315
+
A generator composition is a natural way to insert a flow of one generator into another. It doesn't use extra memory to store intermediate results.
322
316
323
317
## "yield" is a two-way road
324
318
325
-
Till this moment, generators were like "iterators on steroids". And that's how they are often used.
326
-
327
-
But in fact they are much more powerful and flexible.
319
+
Till this moment, generators were similar to iterable objects, with a special syntax to generate values. But in fact they are much more powerful and flexible.
328
320
329
321
That's because `yield` is a two-way road: it not only returns the result outside, but also can pass the value inside the generator.
330
322
@@ -336,7 +328,7 @@ Let's see an example:
336
328
function*gen() {
337
329
*!*
338
330
// Pass a question to the outer code and wait for an answer
339
-
let result =yield"2 + 2?"; // (*)
331
+
let result =yield"2 + 2 = ?"; // (*)
340
332
*/!*
341
333
342
334
alert(result);
@@ -351,7 +343,7 @@ generator.next(4); // --> pass the result into the generator
351
343
352
344

353
345
354
-
1. The first call `generator.next()` is always without an argument. It starts the execution and returns the result of the first `yield` ("2+2?"). At this point the generator pauses the execution (still on that line).
346
+
1. The first call `generator.next()` is always without an argument. It starts the execution and returns the result of the first `yield"2+2=?"`. At this point the generator pauses the execution (still on that line).
355
347
2. Then, as shown at the picture above, the result of `yield` gets into the `question` variable in the calling code.
356
348
3. On `generator.next(4)`, the generator resumes, and `4` gets in as the result: `let result = 4`.
357
349
@@ -370,20 +362,20 @@ To make things more obvious, here's another example, with more calls:
370
362
371
363
```js run
372
364
function*gen() {
373
-
let ask1 =yield"2 + 2?";
365
+
let ask1 =yield"2 + 2 = ?";
374
366
375
367
alert(ask1); // 4
376
368
377
-
let ask2 =yield"3 * 3?"
369
+
let ask2 =yield"3 * 3 = ?"
378
370
379
371
alert(ask2); // 9
380
372
}
381
373
382
374
let generator =gen();
383
375
384
-
alert( generator.next().value ); // "2 + 2?"
376
+
alert( generator.next().value ); // "2 + 2 = ?"
385
377
386
-
alert( generator.next(4).value ); // "3 * 3?"
378
+
alert( generator.next(4).value ); // "3 * 3 = ?"
387
379
388
380
alert( generator.next(9).done ); // true
389
381
```
@@ -408,12 +400,12 @@ As we observed in the examples above, the outer code may pass a value into the g
408
400
409
401
To pass an error into a `yield`, we should call `generator.throw(err)`. In that case, the `err` is thrown in the line with that `yield`.
410
402
411
-
For instance, here the yield of `"2 + 2?"` leads to an error:
403
+
For instance, here the yield of `"2 + 2 = ?"` leads to an error:
412
404
413
405
```js run
414
406
function*gen() {
415
407
try {
416
-
let result =yield"2 + 2?"; // (1)
408
+
let result =yield"2 + 2 = ?"; // (1)
417
409
418
410
alert("The execution does not reach here, because the exception is thrown above");
419
411
} catch(e) {
@@ -438,7 +430,7 @@ The current line of the calling code is the line with `generator.throw`, labelle
438
430
439
431
```js run
440
432
function*generate() {
441
-
let result =yield"2 + 2?"; // Error in this line
433
+
let result =yield"2 + 2 = ?"; // Error in this line
0 commit comments