# 生成器和迭代器
# Generator
常规函数只会返回一个单一值(或者不返回任何值)。
而 Generator 可以按需一个接一个地返回(“yield”)多个值。它们可与 iterable 完美配合使用,从而可以轻松地创建数据流。
# Generator 函数
要创建一个 generator,我们需要一个特殊的语法结构:function*
,即所谓的 “generator function”。
Generator 函数与常规函数的行为不同。在此类函数被调用时,它不会运行其代码。而是返回一个被称为 “generator object” 的特殊对象,来管理执行流程。
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
// "generator function" 创建了一个 "generator object"
let generator = generateSequence();
alert(generator); // [object Generator]
一个 generator 的主要方法就是 next(),当被调用时,它会恢复函数的运行,执行直到最近的 yield <value>
语句。
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
let one = generator.next();
console.log(JSON.stringify(one)); // {value: 1, done: false}
# Generator 是可迭代的
当你看到 next() 方法,或许你已经猜到了 generator 是 可迭代(iterable)的。(译注:next() 是 iterator 的必要方法)
我们可以使用 for..of 循环遍历它所有的值:
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
for (let value of generator) {
alert(value); // 1,然后是 2
}
for..of 写法是不是看起来比 .next().value 优雅多了?
……但是请注意:上面这个例子会先显示 1,然后是 2,然后就没了。它不会显示 3!
这是因为当 done: true 时,for..of 循环会忽略最后一个 value。因此,如果我们想要通过 for..of 循环显示所有的结果,我们必须使用 yield 返回它们
# Iterator
异步迭代允许我们对按需通过异步请求而得到的数据进行迭代。例如,我们通过网络分段(chunk-by-chunk)下载数据时。异步生成器(generator)使这一步骤更加方便。
# 可迭代对象
假设我们有一个对象,例如下面的 range:
我们想对它使用 for..of 循环,例如 for(value of range),来获取从 1 到 5 的值。
换句话说,我们想向对象 range 添加 迭代能力。
这可以通过使用一个名为 Symbol.iterator 的特殊方法来实现:
- 当循环开始时,该方法被 for..of 结构调用,并且它应该返回一个带有 next 方法的对象。
- 对于每次迭代,都会为下一个值调用 next() 方法。
- next() 方法应该以
{done: true/false, value:<loop value>}
的格式返回一个值,其中 done:true 表示循环结束。
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
// 在 for..of 循环开始时被调用一次
return {
current: this.from,
last: this.to,
next() {
// 每次迭代时都会被调用,来获取下一个值
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
}
};
for (let value of range) {
alert(value); // 1,然后 2,然后 3,然后 4,然后 5
}
# 异步可迭代对象
当值是以异步的形式出现时,例如在 setTimeout 或者另一种延迟之后,就需要异步迭代。
最常见的场景是,对象需要发送一个网络请求以传递下一个值,稍后我们将看到一个它的真实示例。
要使对象异步迭代:
- 使用
Symbol.asyncIterator
取代Symbol.iterator
。 next()
方法应该返回一个promise
(带有下一个值,并且状态为fulfilled
)。- 关键字
async
可以实现这一点,我们可以简单地使用async next()
。
- 关键字
- 我们应该使用 for await (let item of iterable) 循环来迭代这样的对象。
- 注意关键字
await
。
- 注意关键字
let range = {
from: 1,
to: 5,
[Symbol.asyncIterator]() {
// (1)
return {
current: this.from,
last: this.to,
async next() {
// (2)
// 注意:我们可以在 async next 内部使用 "await"
await new Promise((resolve) => setTimeout(resolve, 1000)); // (3)
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
}
};
(async () => {
for await (let value of range) {
// (4)
alert(value); // 1,2,3,4,5
}
})();
# 结合 generator
现在,让我们回顾一下 generator,它使我们能够写出更短的迭代代码。在大多数时候,当我们想要创建一个可迭代对象时,我们会使用 generator。
let range = {
from: 1,
to: 5,
*[Symbol.iterator]() { // [Symbol.iterator]: function*() 的一种简写
for(let value = this.from; value <= this.to; value++) {
yield value;
}
}
};
for(let value of range) {
alert(value); // 1,然后 2,然后 3,然后 4,然后 5
}
async function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
// 哇,可以使用 await 了!
await new Promise(resolve => setTimeout(resolve, 1000));
yield i;
}
}
(async () => {
let generator = generateSequence(1, 5);
for await (let value of generator) {
alert(value); // 1,然后 2,然后 3,然后 4,然后 5(在每个 alert 之间有延迟)
}
})();