面试官:几分钟搞懂异步迭代和生成器
异步的迭代和生成器
异步迭代允许我们迭代异步的、按需的数据。比如,当我们通过网络一块一块地下载东西的时候。而异步生成器使它更加方便。
让我们先看一个简单的示例,以掌握语法,然后回顾一个实际的用例。
回顾迭代
让我们回顾一下关于可迭代对象的话题。我们有一个对象,比如range:
let range = {
from: 1,
to: 5
};
我们想用for..of
循环,例如for(value of range)
,得到1到5的值。换句话说,我们希望向对象添加迭代功能。可以使用名称Symbol.iterator
的特殊方法实现:
当循环开始时,它应该返回一个带有下一个方法的对象。
对于每次迭代,都会为下一个值调用
next()
方法。next()
应该以{done: true/false, value:<循环值>}
的形式返回值,其中done:true
表示循环结束。
以下是可迭代对象范围的实现:
let range = {
from: 1,
to: 5,
[Symbol.iterator]() { // called once, in the beginning of for..of
return {
current: this.from,
last: this.to,
next() { // called every iteration, to get the next value
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
}
};
for(let value of range) {
alert(value); // 1 then 2, then 3, then 4, then 5
}
如果有任何不清楚的地方,请访问Iterables这一章,它提供了关于常规Iterables的所有细节。
异步迭代
当值异步地出现时,需要异步迭代:在setTimeout
或其他类型的延迟之后。
最常见的情况是对象需要发出网络请求来传递下一个值,稍后我们将看到一个真实的例子。
使对象异步可迭代:
使用 Symbol.asyncIterator
代替Symbol.iterator
。next()方法应该返回一个承诺(用下一个值来实现)。 async关键字处理它,我们可以简单地使用async next()。 要遍历这样一个对象,应该使用for await (let item of iterable)循环。
作为一个开始的例子,让我们创建一个可迭代的range对象,类似于前面的对象,但是现在它将异步返回值,每秒返回一个值。
我们需要做的就是在上面的代码中执行一些替换:
let range = {
from: 1,
to: 5,
[Symbol.asyncIterator]() { // (1)
return {
current: this.from,
last: this.to,
async next() { // (2)
// note: we can use "await" inside the async next:
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
}
})()
正如我们所见,该结构类似于常规迭代器:
要使一个对象异步可迭代,它必须有一个方法
Symbol.asyncIterator
(1)。这个方法必须返回一个对象,
next()
方法返回一个promise
(2)。next()方法不一定是异步的,它可以是一个返回promise的常规方法,但异步允许我们使用await,所以这很方便。这里我们稍微延迟一下(3)。
为了进行迭代,我们使用
for await(let value of range)
(4),即在for
后添加await
。它调用range[Symbol.asyncIterator]()
一次,然后调用next()
获取值。
这里是一个小表格,有不同之处:
回顾生成器
现在让我们回顾一下生成器,因为它们可以使迭代代码变得更短。大多数时候,当我们想要创建一个可迭代对象时,我们会使用生成器。
为了简单起见,省略一些重要的东西,它们是“产生(产生)值的函数”。它们将在章节生成器中详细解释。
生成器用函数*(注意星号)标记,并使用yield生成一个值,然后我们可以使用for..把它们循环起来。
这个例子生成了一个从开始到结束的值序列:
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
for(let value of generateSequence(1, 5)) {
alert(value); // 1, then 2, then 3, then 4, then 5
}
我们已经知道,要使对象可迭代,我们应该添加Symbol.iterator
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
return <object with next to make range iterable>
}
}
一种常用的Symbol.iterator
返回一个生成器,它使代码更短,如你所见:
let range = {
from: 1,
to: 5,
*[Symbol.iterator]() { // a shorthand for [Symbol.iterator]: function*()
for(let value = this.from; value <= this.to; value++) {
yield value;
}
}
};
for(let value of range) {
alert(value); // 1, then 2, then 3, then 4, then 5
}
异步生成器
对于大多数实际应用程序,当我们想要创建一个异步生成一系列值的对象时,我们可以使用异步生成器。
语法很简单:将function*
与async
绑定。这使得发电机是异步的。
然后使用for await(…)
对其进行迭代,如下所示:
async function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
// Wow, can use 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, then 2, then 3, then 4, then 5 (with delay between)
}
})();
由于生成器是异步的,我们可以在其中使用await
、依赖 Promises
、执行网络请求等等。
异步迭代 range
常规生成器可以用作Symbol.iterator
,使迭代代码更短。
与此类似,异步生成器也可以用作Symbol.asyncIterator
来实现异步迭代。
let range = {
from: 1,
to: 5,
// this line is same as [Symbol.asyncIterator]: async function*() {
async *[Symbol.asyncIterator]() {
for(let value = this.from; value <= this.to; value++) {
// make a pause between values, wait for something
await new Promise(resolve => setTimeout(resolve, 1000));
yield value;
}
}
};
(async () => {
for await (let value of range) {
alert(value); // 1, then 2, then 3, then 4, then 5
}
})();