11# Generators
22
3- 通常情况下,函数都只会返回一个值或者什么也不返回 。
3+ 常规函数只会返回一个具体值(或者 ` undefined ` ) 。
44
5- Generators 可以按需一个个返回 (“yield”)多个值,可以是无限数量个值 。它们与 [ iterables] ( info:iterable ) 配合使用,可以轻松创建数据流。
5+ 而 Generators 可以按需逐个生成 (“yield”)多个值。它们与 [ iterables] ( info:iterable ) 配合使用,可以轻松创建数据流。
66
77## Generator 函数
88
@@ -18,24 +18,35 @@ function* generateSequence() {
1818}
1919```
2020
21- “generator 函数”这个术语听起来有点误导,因为我们在调用它时候并不会执行代码。相反,他返回一个特殊的对象,我们称为 “generator 对象”。
21+ “generator 函数”与常规函数的运行表现有所不同,当执行“generator 函数”时,它并不直接执行完 ** 函数体 ** 的代码,而是返回一个特殊的对象,即 “generator 对象”,来管理执行流程 。
2222
23- 因此,它是一种“generator 构造器函数”。
23+ 来打印一下这种对象:
2424
2525``` js
26- // “generator 函数”创建“generator 对象”
26+ function * generateSequence () {
27+ yield 1 ;
28+ yield 2 ;
29+ return 3 ;
30+ }
31+
32+ // "generator function"(指 generateSequence()) 创建了一个 "generator 对象"
2733let generator = generateSequence ();
34+ * ! *
35+ alert (generator); // [object Generator]
36+ */ ! *
2837```
2938
30- ` generator ` 对象类似于“冻结函数调用(frozen function call)”:
39+ 上面的代码中, ** 函数体 ** 代码还没有开始执行:
3140
3241![ ] ( generateSequence-1.svg )
3342
34- 在创建后,代码在一开始就暂停执行 。
43+ generator 对象的主要方法是 ` next() ` 。被调用时,它会恢复上面的执行过程直到最近的 ` yield <value> ` 语句( ` value ` 可以省略,默认为 ` undefined ` )。然后代码再次暂停执行,并将值返回给外部代码 。
3544
36- Generator 的主要方法是 ` next() ` 。调用它后,就会恢复上面的执行过程直到最近的 ` yield <value> ` 语句。然后代码再次暂停执行,并将值返回到外部代码。
45+ ` next() ` 调用结果总是一个包含两个属性的对象:
46+ - ` value ` : “generator 函数”每次 ** 产出(yielded)** 的值。(译者注:yield翻译为产出,是为了配合 ** 生成器(generator)** 的语义。)
47+ - ` done ` : ` true ` 表示“generator 函数”已经执行完成,否则为 ` false ` 。
3748
38- 例如,这里我们创建了 generator 并获取其第一个 yielded 值 :
49+ 举个例子,下面我们创建一个 generator 并获取其第一个产出的值 :
3950
4051``` js run
4152function * generateSequence () {
@@ -53,15 +64,11 @@ let one = generator.next();
5364alert (JSON .stringify (one)); // {value: 1, done: false}
5465```
5566
56- ` next() ` 的结果总是一个对象:
57- - ` value ` :yielded 值。
58- - ` done ` :如果代码没有执行完,其值为 ` false ` ,否则就是 ` true ` 。
59-
60- 截至目前,我们只获得了第一个值:
67+ 截至目前,我们只获得了第一个值,函数体停在了第二行:
6168
6269![ ] ( generateSequence-2.svg )
6370
64- 我们再次调用 ` generator.next() ` 。代码恢复执行并返回下一个 ` yield ` :
71+ 再次调用 ` generator.next() ` 。代码恢复执行并返回下一个 ` yield ` 的产出值 :
6572
6673``` js
6774let two = generator .next ();
@@ -81,23 +88,19 @@ alert(JSON.stringify(three)); // {value: 3, *!*done: true*/!*}
8188
8289![ ] ( generateSequence-4.svg )
8390
84- 现在,generator 已经执行完成了。我们通过 ` done:true ` 和处理的最终结果 ` value:3 ` 可以看出来。
85-
86- 此时如果再调用 ` generator.next() ` 将不起任何作用。如果我们还是执行此语句,那么它将会返回相同的对象:` {done: true} ` 。
91+ 我们通过 ` done:true ` 可以看出函数执行完成了,此时 ` value:3 ` 作为函数执行的最终结果。
8792
88- 对于 generator 来说,没有办法去“回滚”它的操作。但是我们可以通过调用 ` generateSequence() ` 来创建另一个 generator。
89-
90- 到目前为止,最重要的是要理解 generator 函数与常规函数不同,generator 函数不运行代码。它们是作为“generator 工厂”。运行 ` function* ` 返回一个 generator,然后我们调用它获得需要的值。
93+ 再调用 ` generator.next() ` 已经没有什么意义了。这将总是返回相同的对象:` {done: true} ` 。
9194
9295```smart header="` function* f(…) ` 或者 ` function *f(…) ` ?"
9396这是一个小的书写习惯问题,两者的语法都是正确的。
9497
95- 但是通常首选第一种语法,因为星号 ` * ` 表示它是一个 generator 函数,它描述的是函数种类而不是名称,因此它仍应使用 ` function ` 关键字 。
98+ 但是通常首选第一种语法,因为星号 ` * ` 表示它是一个 generator 函数,它描述的是函数种类而不是名称,因此 ` * ` 应该和 ` function ` 关键字紧贴一起 。
9699```
97100
98101## Generators 是可迭代的
99102
100- 你可能通过 `next()` 方法了解到 generator 是[可迭代](info:iterable)的。
103+ 看到 `next()` 方法,或许你都猜到了 generator 是 [可迭代](info:iterable) 的。(译者注:`next()` 是 iterator 的必要方法)
101104
102105我们可以通过 `for..of` 循环迭代所有值:
103106
@@ -115,11 +118,11 @@ for(let value of generator) {
115118}
116119```
117120
118- 这样的方法看上去要比一个个调用 ` .next().value ` 好得多,不是吗 ?
121+ ` for ... of ` 写法是不是比 ` .next().value ` 优雅多了 ?
119122
120123……但是请注意:上面的迭代例子中,它先显示 ` 1 ` ,然后是 ` 2 ` 。它不会显示 ` 3 ` !
121124
122- 这是因为当 ` done: true ` 时,for-of 循环会忽略最后一个 ` value ` 。因此,如果我们想要通过 ` for..of ` 循环显示所有结果 ,我们必须使用 ` yield ` 而不是 ` return ` 返回它们 :
125+ 这是因为当 ` done: true ` 时,for-of 循环会忽略最后一个 ` value ` 。因此,如果我们想要通过 ` for..of ` 循环显示所有结果时 ,我们必须使用 ` yield ` 而不是 ` return ` :
123126
124127``` js run
125128function * generateSequence () {
@@ -137,7 +140,7 @@ for(let value of generator) {
137140}
138141```
139142
140- 当然, 由于 generators 是可迭代的,我们可以调用所有相关的函数 ,例如:spread 操作 ` ... ` :
143+ 由于 generators 是可迭代的,我们可以充分发挥 ES6 中 iterator 的特性 ,例如:spread 操作 ` ... ` :
141144
142145``` js run
143146function * generateSequence () {
@@ -151,7 +154,7 @@ let sequence = [0, ...generateSequence()];
151154alert (sequence); // 0, 1, 2, 3
152155```
153156
154- 在上面的代码中,` ...generateSequence() ` 将 iterable 转换为 item 的数组 (关于 spread 操作可以参见相关章节 [ ] ( info:rest-parameters-spread-operator#spread-operator ) )。
157+ 在上面的代码中,` ...generateSequence() ` 将可迭代的“generator 对象”转换为了一个数组 (关于 spread 操作可以参见相关章节 [ ] ( info:rest-parameters-spread-operator#spread-operator ) )。
155158
156159## 使用 generator 进行迭代
157160
@@ -167,14 +170,14 @@ let range = {
167170 // for..of range 在一开始就调用一次这个方法
168171 [Symbol .iterator ]() {
169172 // ……它返回 iterator 对象:
170- // 向前, for..of 仅适用于该对象,请求下一个值
173+ // 后续的操作中, for..of 将只针对这个 iterator 对象,通过不断的调用 next() 来获取下一个值
171174 return {
172175 current: this .from ,
173176 last: this .to ,
174177
175178 // for..of 在每次迭代的时候都会调用 next()
176179 next () {
177- // 它应该返回对象 {done:.., value :...} 值
180+ // 必须返回特定结构的对象: {done:.., value :...}
178181 if (this .current <= this .last ) {
179182 return { done: false , value: this .current ++ };
180183 } else {
@@ -185,28 +188,13 @@ let range = {
185188 }
186189};
187190
191+ // 迭代整个 range 对象,返回从 `range.from` 到 `range.to` 范围的所有数字的数组。
188192alert ([... range]); // 1,2,3,4,5
189193```
190194
191- 使用 generator 来生成可迭代序列更简单,更优雅:
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- ## 转换 Symbol.iterator 为 generator
206-
207- 我们可以通过提供一个 generator 作为 ` Symbol.iterator ` 来向任何自定义对象添加 generator-style 的迭代。
195+ 我们可以通过提供一个 generator 函数作为对象的 ` Symbol.iterator ` 来使任何对象可被迭代。
208196
209- 这是相同的 ` range ` ,但是使用的是一个更紧凑的 iterator :
197+ 以下是使用的另一种更紧凑的写法的 ` range ` 对象 :
210198
211199``` js run
212200let range = {
@@ -223,15 +211,15 @@ let range = {
223211alert ( [... range] ); // 1,2,3,4,5
224212```
225213
226- 正常工作 ,因为 ` range[Symbol.iterator]() ` 现在返回一个 generator,而 generator 方法正是 ` for..of ` 所期待的 :
214+ 代码正常工作 ,因为 ` range[Symbol.iterator]() ` 现在返回一个 generator,而 generator 方法正是 ` for..of ` 所期望的 :
227215- 它具有 ` .next() ` 方法
228216- 它以 ` {value: ..., done: true/false} ` 的形式返回值
229217
230218当然,这不是巧合,Generators 被添加到 JavaScript 语言中时也考虑了 iterators,以便更容易实现。
231219
232- 带有 generator 的最后一个变体比 ` range ` 的原始可迭代代码简洁得多,并保持了相同的功能 。
220+ 带有 generator 的 ` range ` 对象比的原始可迭代代码简洁得多,还保持了功能的一致 。
233221
234- ``` smart header="Generators 可能永远 generate 值 "
222+ ``` smart header="Generators 可以永远产生值 "
235223在上面的例子中,我们生成了有限序列,但是我们也可以创建一个生成无限序列的 generator,它可以一直 yield 值。例如,无序的伪随机数序列。
236224
237225这种情况下的 `for..of` generator 需要一个 `break`(或者 `return`)语句,否则循环将永远重复并挂起。
@@ -241,18 +229,26 @@ alert( [...range] ); // 1,2,3,4,5
241229
242230Generator 组合是 generator 的一个特殊功能,它可以显式地将 generator “嵌入”到一起。
243231
244- 例如,我们想要生成一个序列:
245- - 数字 ` 0..9 ` (ASCII 可显示字符代码为 48..57),
246- - 后跟字母 ` a..z ` (ASCII 可显示字符代码为 65..90)
247- - 后跟大写字母 ` A..Z ` (ASCII 可显示字符代码为 97..122)
232+ 如下,我们有个生成数字序列的函数:
248233
249- 我们可以使用序列,比如通过从中选择字符来创建密码(也可以添加语法字符),但是我们先生成它。
234+ ``` js
235+ function * generateSequence (start , end ) {
236+ for (let i = start; i <= end; i++ ) yield i;
237+ }
238+ ```
239+
240+ 接着,我们复用这个函数来生成更加复杂的包含三部分的序列:
241+ - 第一部分为数字 ` 0..9 ` (ASCII 可显示字符代码为 48..57),
242+ - 第二部分为大写字母字母 ` A..Z ` (ASCII 可显示字符代码为 65..90)
243+ - 第三部分为小写字母 ` a...z ` (ASCII 可显示字符代码为 97..122)
250244
251- 我们已经有了 ` function* generateSequence(start, end) ` 。让我们重复使用它来一个个地传递 3 个序列,它真是我们所需要的 。
245+ 我们可以在这个序列中选择字符来创建密码(也可以添加其他特殊字符),现在先编写这个生成器 。
252246
253- 在普通函数中,为了将多个其他函数的结果组合到一起,我们先调用它们,然后将他们的结果存储起来,最后将它们合并到一起 。
247+ 在常规函数的调用中,为了组合多个函数的结果,我们需要先依次调用它们,并分别将他们的结果存储起来,最后统一将它们合并到一起 。
254248
255- 对于 generators,我们可以更好地去实现,就像这样:
249+ 对于 generators 而言,我们可以使用 ` yield* ` 这个语法来将一个 generator 嵌入(组合)到另一个 generator 中:
250+
251+ 组合式 generator 的例子:
256252
257253``` js run
258254function * generateSequence (start , end ) {
@@ -283,9 +279,9 @@ for(let code of generatePasswordCodes()) {
283279alert (str); // 0..9A..Za..z
284280```
285281
286- 示例中的特殊 ` yield* ` 指令负责组合。它将执行 ** 委托** 给另一个 generator。或者简单来说就是 ` yield* gen ` 迭代 generator ` gen ` 并显式地将其 yield 结果转发到外部。好像这些值是由外部 generator yield 一样 。
282+ 示例中的 ` yield* ` 指令负责将执行 ** 委托** 给另一个 generator。或者简单来说就是 ` yield* gen ` 迭代了名为 ` gen ` 的 generator 并显式地将 ` gen ` yield 的结果转发到最外部。好像这些值是由外部的 generator yield 的一样 。
287283
288- 结果就像是我们从嵌套的 generators 内联的代码一样 :
284+ 执行结果和我们将嵌套的 generators 中的代码直接内联到外层generator一样 :
289285
290286``` js run
291287function * generateSequence (start , end ) {
@@ -316,19 +312,15 @@ for(let code of generateAlphaNum()) {
316312alert (str); // 0..9A..Za..z
317313```
318314
319- Generator 组合是将一个 generator 流插入到另一个 generator 的自然的方式。
320-
321- 即使来自嵌套 generator 的值的流是无限的,它也可以正常工作。它很简单,不需要使用额外的内存来存储中间结果。
322-
323- ## “yield” 双向路径(two-way road)
315+ ** Generator 组合** 是将一个 generator 流自然地插入到另一个 generator 的方式。它不需要使用额外的内存来存储中间结果。
324316
325- 直到此时,generators 就像“固醇(steroids)上的 iterators”。这就是它经常被使用的方式。
317+ ## “yield” 双向路径(two-way street)
326318
327- 但是实际上 ,generators 要更强大,更灵活 。
319+ 目前看来 ,generators 和可迭代对象非常相似,仅仅是其产生 value 的语法有所不同。但实际上,generators 更加高效和灵活 。
328320
329- 这是因为 ` yield ` 是一个双向路径:它不仅向外面返回结果,而且可以传递 generator 内的值 。
321+ 这是因为 ` yield ` 是一个双向路径:它不仅向外面返回结果,而且可以将外部的值传递到 generator 内 。
330322
331- 为此,我们应该使用参数 arg 调用 ` generator.next(arg) ` 。这个参数就成了 ` yield ` 的结果 。
323+ 调用 ` generator.next(arg) ` ,我们就能将值 ` arg ` 传递到了 generator 内部。这个 ` arg ` 参数会变成 ` yield ` 语句的结果(返回值) 。
332324
333325我们来看一个例子:
334326
@@ -364,9 +356,9 @@ generator.next(4); // --> 向 generator 传入结果
364356setTimeout (() => generator .next (4 ), 1000 );
365357```
366358
367- 我们可以看到,与普通函数不同 ,generators 和调用代码可以通过传递 ` next/yield ` 中的值来交换结果 。
359+ 我们可以看到,与常规函数不同 ,generators 内部和外部调用环境可以通过 ` next/yield ` 来传递值,以交换结果 。
368360
369- 为了使事情浅显易懂 ,我们来看另一个有更多调用的例子:
361+ 为了使以上要点浅显易懂 ,我们来看另一个有更多调用的例子:
370362
371363``` js run
372364function * gen () {
@@ -406,7 +398,7 @@ alert( generator.next(9).done ); // true
406398
407399……但是它也可以在那里发起(抛出)错误。这很自然,因为错误本身也是一种结果。
408400
409- 要向 ` yield ` 传递错误,我们应该调用 ` generator.throw(err) ` 。在那种情况下 ,` err ` 与 ` yield ` 一起被抛出 。
401+ 要向 ` yield ` 传递错误,我们应该调用 ` generator.throw(err) ` 。然后 ,` err ` 将在对应的 ` yield ` 那一行被抛出 。
410402
411403例如,` "2 + 2?" ` 的 yield 导致一个错误:
412404
@@ -454,7 +446,7 @@ try {
454446*/ ! *
455447```
456448
457- 如果我们在那里捕获错误,那么像往常一样,它会转到外部代码 (如果有的话),如果没有捕获 ,则会结束脚本。
449+ 通常,如果我们没有在那里捕获错误,它会将错误转到外部代码 (如果有的话),如果外部也没有捕获错误 ,则会结束脚本。
458450
459451## 总结
460452
0 commit comments