生成器是可以暂停和恢复的功能。它们使用function*
和yield
关键词简化迭代器创作。声明为function*
的函数返回一个Generator
实例。生成器是迭代器的子类型,包括附加的next
和throw
函数。这些使值能够流回生成器,因此yield
是返回或抛出一个值的表达式形式。
如前一段所述,生成器是可以暂停和恢复的功能。让我们看一下下面这个简单的例子,以便更好地理解生成器:
代码清单 145
function* sampleFunc() {
console.log('First');
yield;
console.log('Second');
}
let gen = sampleFunc();
console.log(gen.next());
console.log(gen.next());
在这里,我们创建了一个名为sampleFunc
的生成器。请注意,我们使用yield
关键字在函数中间暂停。还要注意,当我们调用函数时,它不会被执行。在我们迭代函数之前,它不会被执行,所以这里我们使用next
函数迭代它。next
功能将一直执行,直到达到yield
状态。这里它将暂停,直到调用另一个next
功能。这又会恢复并继续执行,直到函数结束。
让我们看一下输出,以获得进一步的说明:
代码清单 146
First
{ value: undefined, done: false }
Second
{ value: undefined, done: true }
现在让我们进入下一部分,看看生成器迭代。
让我们看一个例子,创建一个生成器,然后使用它进行迭代。考虑下面的代码示例:
代码清单 147
let fibonacci = {
[Symbol.iterator]: function*()
{
let pre =
0, cur =
1;
for (;;) {
let temp =
pre;
pre = cur;
cur += temp;
yield cur;
}
}
}
for (let n of
fibonacci) {
// truncate the sequence at 100
if (n >
100)
break;
console.log(n);
}
从前面的代码中可以看出,生成器确实是迭代器的一个子类型。语法非常相似,只是有一些变化。值得注意的是,你会看到function*
和yield
关键词。
以下是前面代码的输出:
代码清单 148
1
2
3
5
8
13
21
34
55
89
如您所见,我们得到了与编写自己的迭代器时相同的输出。
让我们用更多的 ES6 风格的编码再写一遍:
代码清单 149
let fibonacci = {
*[Symbol.iterator]() {
let pre =
0, cur =
1;
for (;;) {
[ pre, cur ] = [ cur, pre + cur ];
yield cur;
}
}
}
for (let n of
fibonacci) {
if (n >
100)
break;
console.log(n);
}
可以看到,星号(*
)现在在[Symbol.iterator]
定义上。还要注意对象析构的用法。这使得代码更加简洁明了。
以下是前面代码的输出:
代码清单 150
1
2
3
5
8
13
21
34
55
89
让我们考虑另一个例子,在没有迭代器的情况下直接使用生成器:
代码清单 151
function* range (start, end, step) {
while (start < end) {
yield start;
start += step;
}
}
for (let i of
range(0, 10,
2)) {
console.log(i);
}
这里,我们定义了一个以start
、end
和step
为参数的生成器。当我们到达yield
时,我们暂停。接下来,我们以step
的量执行start
加法。这一直持续到我们到达end
。
以下是前面代码的输出:
代码清单 152
0
2
4
6
8
可以使用生成器,并通过数组匹配支持析构。让我们看看下面的例子:
代码清单 153
let fibonacci = function*
(numbers) {
let pre =
0, cur =
1;
while (numbers-- > 0) {
[ pre, cur ] = [ cur, pre + cur ];
yield cur;
}
}
for (let n of
fibonacci(5))
console.log(n);
let numbers = [
...fibonacci(5) ];
console.log(numbers);;
let [ n1, n2, n3, ...others ] =
fibonacci(5);
console.log(others[0]);
在前面的代码中,我们首先创建了一个生成器。接下来,我们对生成器执行简单的迭代,进行五次迭代。接下来,我们使用spread
运算符为数字数组赋值。最后,我们在数组中使用模式匹配,并将生成器函数中的值分配给其他人。
下面是前面代码的输出:
代码清单 154
1
2
3
5
8
[ 1, 2, 3, 5, 8 ]
5
发电机的承诺之一是控制流量。这在处理异步编程时变得很重要。我们从承诺中看到了很多,我们将在另一章中看到。让我们看看如何在使用生成器时处理控制流。看看下面的例子:
代码清单 155
function async (proc, ...params) {
var iterator = proc(...params);
return new
Promise((resolve, reject) => {
let loop =
(value) => {
let result;
try {
result = iterator.next(value);
}
catch (err) {
reject(err);
}
if (result.done) {
resolve(result.value);
}
else if
(typeof result.value === "object"
&& typeof
result.value.then === "function")
result.value.then((value) => {
loop(value);
}, (err) => {
reject(err);
})
else {
loop(result.value);
}
};
loop();
})
}
// application-specific asynchronous builder
function makeAsync (text, after) {
return new
Promise((resolve, reject) => {
setTimeout(() => resolve(text), after);
})
}
// application-specific asynchronous procedure
async(function*
(greeting) {
let foo =
yield makeAsync("foo", 300);
let bar =
yield makeAsync("bar", 200);
let baz =
yield makeAsync("baz", 100);
return `${greeting} ${foo} ${bar} ${baz}`;
},
"Hello").then((msg) => {
console.log(msg);
});
好吧,让我们把它分解一下。第一个函数async
,
接收一个生成器作为第一个参数以及随后的任何其他参数。在这种情况下,它接收Hello
。一个新的承诺被创造出来,它将贯穿所有的迭代。在这种情况下,它将正好循环四次。第一次,值是未定义的,我们在传入值时调用iterator.next()
。所有后续时间,数值代表foo
、bar
和baz
。一旦我们用尽了所有的迭代,我们就完成了循环。
下一个函数makeAsync
,只是取一些文本,等待一段设定的时间。它使用setTimeout
功能来模拟真实世界的场景。
最后一个代码段是我们对async
函数的执行。我们传入一个生成器函数,该函数接受一个问候语作为参数,并包含三个yield
语句和一个return
。它调用async
并执行迭代,同时遵守各种setTimout
规范。这里好的一点是,不管每次迭代何时调用return
,我们仍然有看起来非常同步的代码。在迭代完成之前,我们不会显示我们的消息。
下面是前面代码的输出:
代码清单 156
Hello foo bar baz
生成器方法支持类中的方法和基于生成器函数的对象。考虑以下示例:
代码清单 157
class GeneratorClass {
* sampleFunc() {
console.log('First');
yield;
console.log('Second');
}
}
let gc = new GeneratorClass();
let gen =
gc.sampleFunc();
console.log(gen.next());
console.log(gen.next());
就像我们的第一个例子一样,我们现在在 ES6 类中使用生成器。同样像第一个例子,我们得到相同的输出:
代码清单 158
First
{ value: undefined, done: false }
Second
{ value: undefined, done: true }