Skip to content

Commit 7e32cf2

Browse files
committed
minor
1 parent 816d193 commit 7e32cf2

File tree

4 files changed

+92
-100
lines changed

4 files changed

+92
-100
lines changed

1-js/12-generators-iterators/1-generators/article.md

+55-63
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Regular functions return only one, single value (or nothing).
44

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.
66

77
## Generator functions
88

@@ -18,22 +18,33 @@ function* generateSequence() {
1818
}
1919
```
2020

21-
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.
2222

23-
So it's kind of a "generator constructor".
23+
Here, take a look:
24+
25+
```js run
26+
function* generateSequence() {
27+
yield 1;
28+
yield 2;
29+
return 3;
30+
}
2431

25-
```js
2632
// "generator function" creates "generator object"
2733
let generator = generateSequence();
34+
*!*
35+
alert(generator); // [object Generator]
36+
*/!*
2837
```
2938

30-
The `generator` object is something like an "frozen function call":
39+
The function code execution hasn't started yet:
3140

3241
![](generateSequence-1.svg)
3342

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.
3544

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`.
3748

3849
For instance, here we create the generator and get its first yielded value:
3950

@@ -53,11 +64,7 @@ let one = generator.next();
5364
alert(JSON.stringify(one)); // {value: 1, done: false}
5465
```
5566

56-
The result of `next()` is always an object:
57-
- `value`: the yielded value.
58-
- `done`: `false` if the code is not finished yet, otherwise `true`.
59-
60-
As of now, we got the first value only:
67+
As of now, we got the first value only, and the function execution is on the second line:
6168

6269
![](generateSequence-2.svg)
6370

@@ -83,14 +90,10 @@ alert(JSON.stringify(three)); // {value: 3, *!*done: true*/!*}
8390

8491
Now the generator is done. We should see it from `done:true` and process `value:3` as the final result.
8592

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}`.
9194

9295
```smart header="`function* f(…)` or `function *f(…)`?"
93-
That's a minor religious question, both syntaxes are correct.
96+
Both syntaxes are correct.
9497

9598
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.
9699
```
@@ -115,11 +118,11 @@ for(let value of generator) {
115118
}
116119
```
117120

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?
119122

120123
...But please note: the example above shows `1`, then `2`, and that's all. It doesn't show `3`!
121124

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`:
123126

124127
```js run
125128
function* generateSequence() {
@@ -137,7 +140,7 @@ for(let value of generator) {
137140
}
138141
```
139142

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 `...`:
141144

142145
```js run
143146
function* generateSequence() {
@@ -151,7 +154,7 @@ let sequence = [0, ...generateSequence()];
151154
alert(sequence); // 0, 1, 2, 3
152155
```
153156

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))
155158

156159
## Using generators for iterables
157160

@@ -185,28 +188,13 @@ let range = {
185188
}
186189
};
187190

191+
// iteration over range returns numbers from range.from to range.to
188192
alert([...range]); // 1,2,3,4,5
189193
```
190194

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`.
208196

209-
Here's the same `range`, but with a much more compact iterator:
197+
Here's the same `range`, but much more compact:
210198

211199
```js run
212200
let range = {
@@ -229,7 +217,7 @@ That works, because `range[Symbol.iterator]()` now returns a generator, and gene
229217

230218
That's not a coincidence, of course. Generators were added to JavaScript language with iterators in mind, to implement them easier.
231219

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.
233221

234222
```smart header="Generators may generate values forever"
235223
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
241229

242230
Generator composition is a special feature of generators that allows to transparently "embed" generators in each other.
243231

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),
246242
- followed by alphabet letters `a..z` (character codes 65..90)
247243
- followed by uppercased letters `A..Z` (character codes 97..122)
248244

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.
252246

253247
In a regular function, to combine results from multiple other functions, we call them, store the results, and then join at the end.
254248

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:
256252

257253
```js run
258254
function* generateSequence(start, end) {
@@ -283,7 +279,7 @@ for(let code of generatePasswordCodes()) {
283279
alert(str); // 0..9A..Za..z
284280
```
285281

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.
287283

288284
The result is the same as if we inlined the code from nested generators:
289285

@@ -316,15 +312,11 @@ for(let code of generateAlphaNum()) {
316312
alert(str); // 0..9A..Za..z
317313
```
318314

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.
322316

323317
## "yield" is a two-way road
324318

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.
328320

329321
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.
330322

@@ -336,7 +328,7 @@ Let's see an example:
336328
function* gen() {
337329
*!*
338330
// Pass a question to the outer code and wait for an answer
339-
let result = yield "2 + 2?"; // (*)
331+
let result = yield "2 + 2 = ?"; // (*)
340332
*/!*
341333

342334
alert(result);
@@ -351,7 +343,7 @@ generator.next(4); // --> pass the result into the generator
351343

352344
![](genYield2.svg)
353345

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).
355347
2. Then, as shown at the picture above, the result of `yield` gets into the `question` variable in the calling code.
356348
3. On `generator.next(4)`, the generator resumes, and `4` gets in as the result: `let result = 4`.
357349

@@ -370,20 +362,20 @@ To make things more obvious, here's another example, with more calls:
370362

371363
```js run
372364
function* gen() {
373-
let ask1 = yield "2 + 2?";
365+
let ask1 = yield "2 + 2 = ?";
374366

375367
alert(ask1); // 4
376368

377-
let ask2 = yield "3 * 3?"
369+
let ask2 = yield "3 * 3 = ?"
378370

379371
alert(ask2); // 9
380372
}
381373

382374
let generator = gen();
383375

384-
alert( generator.next().value ); // "2 + 2?"
376+
alert( generator.next().value ); // "2 + 2 = ?"
385377

386-
alert( generator.next(4).value ); // "3 * 3?"
378+
alert( generator.next(4).value ); // "3 * 3 = ?"
387379

388380
alert( generator.next(9).done ); // true
389381
```
@@ -408,12 +400,12 @@ As we observed in the examples above, the outer code may pass a value into the g
408400

409401
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`.
410402

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:
412404

413405
```js run
414406
function* gen() {
415407
try {
416-
let result = yield "2 + 2?"; // (1)
408+
let result = yield "2 + 2 = ?"; // (1)
417409

418410
alert("The execution does not reach here, because the exception is thrown above");
419411
} catch(e) {
@@ -438,7 +430,7 @@ The current line of the calling code is the line with `generator.throw`, labelle
438430

439431
```js run
440432
function* generate() {
441-
let result = yield "2 + 2?"; // Error in this line
433+
let result = yield "2 + 2 = ?"; // Error in this line
442434
}
443435

444436
let generator = generate();

0 commit comments

Comments
 (0)