面試官:幾分鐘搞懂異步迭代和生成器
異步的迭代和生成器
異步迭代允許我們迭代異步的、按需的數據。比如,當我們通過網絡一塊一塊地下載東西的時候。而異步生成器使它更加方便。
讓我們先看一個簡單的示例,以掌握語法,然后回顧一個實際的用例。
回顧迭代
讓我們回顧一下關于可迭代對象的話題。我們有一個對象,比如range:
let range = {
from: 1,
to: 5
};
我們想用for..of循環(huán),例如for(value of range),得到1到5的值。換句話說,我們希望向對象添加迭代功能。可以使用名稱Symbol.iterator的特殊方法實現:
當循環(huán)開始時,它應該返回一個帶有下一個方法的對象。
對于每次迭代,都會為下一個值調用
next()方法。next()應該以{done: true/false, value:<循環(huán)值>}的形式返回值,其中done:true表示循環(huán)結束。
以下是可迭代對象范圍的實現:
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這一章,它提供了關于常規(guī)Iterables的所有細節(jié)。
異步迭代
當值異步地出現時,需要異步迭代:在setTimeout或其他類型的延遲之后。
最常見的情況是對象需要發(fā)出網絡請求來傳遞下一個值,稍后我們將看到一個真實的例子。
使對象異步可迭代:
使用 Symbol.asyncIterator代替Symbol.iterator。next()方法應該返回一個承諾(用下一個值來實現)。 async關鍵字處理它,我們可以簡單地使用async next()。 要遍歷這樣一個對象,應該使用for await (let item of iterable)循環(huán)。
作為一個開始的例子,讓我們創(chuàng)建一個可迭代的range對象,類似于前面的對象,但是現在它將異步返回值,每秒返回一個值。
我們需要做的就是在上面的代碼中執(zhí)行一些替換:
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
}
})()
正如我們所見,該結構類似于常規(guī)迭代器:
要使一個對象異步可迭代,它必須有一個方法
Symbol.asyncIterator(1)。這個方法必須返回一個對象,
next()方法返回一個promise(2)。next()方法不一定是異步的,它可以是一個返回promise的常規(guī)方法,但異步允許我們使用await,所以這很方便。這里我們稍微延遲一下(3)。
為了進行迭代,我們使用
for await(let value of range)(4),即在for后添加await。它調用range[Symbol.asyncIterator]()一次,然后調用next()獲取值。
這里是一個小表格,有不同之處:
回顧生成器
現在讓我們回顧一下生成器,因為它們可以使迭代代碼變得更短。大多數時候,當我們想要創(chuàng)建一個可迭代對象時,我們會使用生成器。
為了簡單起見,省略一些重要的東西,它們是“產生(產生)值的函數”。它們將在章節(jié)生成器中詳細解釋。
生成器用函數*(注意星號)標記,并使用yield生成一個值,然后我們可以使用for..把它們循環(huán)起來。
這個例子生成了一個從開始到結束的值序列:
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
}
異步生成器
對于大多數實際應用程序,當我們想要創(chuàng)建一個異步生成一系列值的對象時,我們可以使用異步生成器。
語法很簡單:將function*與async綁定。這使得發(fā)電機是異步的。
然后使用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、執(zhí)行網絡請求等等。
異步迭代 range
常規(guī)生成器可以用作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
}
})();
