W3Cschool
恭喜您成為首批注冊(cè)用戶(hù)
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
異步迭代允許我們對(duì)按需通過(guò)異步請(qǐng)求而得到的數(shù)據(jù)進(jìn)行迭代。例如,我們通過(guò)網(wǎng)絡(luò)分段(chunk-by-chunk)下載數(shù)據(jù)時(shí)。異步生成器(generator)使這一步驟更加方便。
首先,讓我們來(lái)看一個(gè)簡(jiǎn)單的示例以掌握語(yǔ)法,然后再看一個(gè)實(shí)際用例。
讓我們回顧一下可迭代對(duì)象的相關(guān)內(nèi)容。
假設(shè)我們有一個(gè)對(duì)象,例如下面的 range
:
let range = {
from: 1,
to: 5
};
我們想對(duì)它使用 for..of
循環(huán),例如 for(value of range)
,來(lái)獲取從 1
到 5
的值。
換句話(huà)說(shuō),我們想向?qū)ο?nbsp;range
添加 迭代能力。
這可以通過(guò)使用一個(gè)名為 Symbol.iterator
的特殊方法來(lái)實(shí)現(xiàn):
for..of
? 結(jié)構(gòu)調(diào)用,并且它應(yīng)該返回一個(gè)帶有 ?next
? 方法的對(duì)象。next()
? 方法。next()
? 方法應(yīng)該以 ?{done: true/false, value:<loop value>}
? 的格式返回一個(gè)值,其中 ?done:true
? 表示循環(huán)結(jié)束。這是可迭代的 range
的一個(gè)實(shí)現(xiàn):
let range = {
from: 1,
to: 5,
[Symbol.iterator]() { // 在 for..of 循環(huán)開(kāi)始時(shí)被調(diào)用一次
return {
current: this.from,
last: this.to,
next() { // 每次迭代時(shí)都會(huì)被調(diào)用,來(lái)獲取下一個(gè)值
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
}
如果有任何不清楚的,你可以閱讀 Iterable object(可迭代對(duì)象) 一章,其中詳細(xì)講解了關(guān)于常規(guī)迭代器(iterator)的所有內(nèi)容。
當(dāng)值是以異步的形式出現(xiàn)時(shí),例如在 setTimeout
或者另一種延遲之后,就需要異步迭代。
最常見(jiàn)的場(chǎng)景是,對(duì)象需要發(fā)送一個(gè)網(wǎng)絡(luò)請(qǐng)求以傳遞下一個(gè)值,稍后我們將看到一個(gè)它的真實(shí)示例。
要使對(duì)象異步迭代:
Symbol.asyncIterator
? 取代 ?Symbol.iterator
?。next()
? 方法應(yīng)該返回一個(gè) ?promise
?(帶有下一個(gè)值,并且狀態(tài)為 ?fulfilled
?)。async
? 可以實(shí)現(xiàn)這一點(diǎn),我們可以簡(jiǎn)單地使用 ?async next()
?。for await (let item of iterable)
? 循環(huán)來(lái)迭代這樣的對(duì)象。await
?。作為開(kāi)始的示例,讓我們創(chuàng)建一個(gè)可迭代的 range
對(duì)象,與前面的那個(gè)類(lèi)似,不過(guò)現(xiàn)在它將異步地每秒返回一個(gè)值。
我們需要做的就是對(duì)上面代碼中的部分代碼進(jìn)行替換:
let range = {
from: 1,
to: 5,
[Symbol.asyncIterator]() { // (1)
return {
current: this.from,
last: this.to,
async next() { // (2)
// 注意:我們可以在 async next 內(nèi)部使用 "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
}
})()
正如我們所看到的,其結(jié)構(gòu)與常規(guī)的 iterator 類(lèi)似:
Symbol.asyncIterator
?(1)?
?。next()
? 方法的對(duì)象,?next()
? 方法會(huì)返回一個(gè) promise ?(2)
?。next()
? 方法可以不是 ?async
? 的,它可以是一個(gè)返回值是一個(gè) ?promise
? 的常規(guī)的方法,但是使用 ?async
? 關(guān)鍵字可以允許我們?cè)诜椒▋?nèi)部使用 ?await
?,所以會(huì)更加方便。這里我們只是用于延遲 1 秒的操作 ?(3)
?。for await(let value of range)
? ?(4)
? 來(lái)進(jìn)行迭代,也就是在 ?for
? 后面添加 ?await
?。它會(huì)調(diào)用一次 ?range[Symbol.asyncIterator]()
? 方法一次,然后調(diào)用它的 ?next()
? 方法獲取值。這是一個(gè)對(duì)比 Iterator 和異步 iterator 之間差異的表格:
Iterator | 異步 iterator | |
---|---|---|
提供 iterator 的對(duì)象方法 | Symbol.iterator
|
Symbol.asyncIterator
|
next() 返回的值是 |
任意值 | Promise
|
要進(jìn)行循環(huán),使用 | for..of
|
for await..of
|
Spread 語(yǔ)法 ?
...
? 無(wú)法異步工作需要常規(guī)的同步 iterator 的功能,無(wú)法與異步 iterator 一起使用。
例如,spread 語(yǔ)法無(wú)法工作:
alert( [...range] ); // Error, no Symbol.iterator
這很正常,因?yàn)樗谕业?nbsp;
Symbol.iterator
,而不是Symbol.asyncIterator
。
for..of
的情況和這個(gè)一樣:沒(méi)有await
關(guān)鍵字時(shí),則期望找到的是Symbol.iterator
。
現(xiàn)在,讓我們回顧一下 generator,它使我們能夠?qū)懗龈痰牡a。在大多數(shù)時(shí)候,當(dāng)我們想要?jiǎng)?chuàng)建一個(gè)可迭代對(duì)象時(shí),我們會(huì)使用 generator。
簡(jiǎn)單起見(jiàn),這里省略了一些解釋?zhuān)?generator 是“生成(yield)值的函數(shù)”。關(guān)于此的詳細(xì)說(shuō)明請(qǐng)見(jiàn) generator 一章。
Generator 是標(biāo)有 function*
(注意星號(hào))的函數(shù),它使用 yield
來(lái)生成值,并且我們可以使用 for..of
循環(huán)來(lái)遍歷它們。
下面這例子生成了從 start
到 end
的一系列值:
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
for(let value of generateSequence(1, 5)) {
alert(value); // 1,然后 2,然后 3,然后 4,然后 5
}
正如我們所知道的,要使一個(gè)對(duì)象可迭代,我們需要給它添加 Symbol.iterator
。
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
return <帶有 next 方法的對(duì)象,以使對(duì)象 range 可迭代>
}
}
對(duì)于 Symbol.iterator
來(lái)說(shuō),一個(gè)通常的做法是返回一個(gè) generator,這樣可以使代碼更短,如下所示:
let range = {
from: 1,
to: 5,
*[Symbol.iterator]() { // [Symbol.iterator]: function*() 的一種簡(jiǎn)寫(xiě)
for(let value = this.from; value <= this.to; value++) {
yield value;
}
}
};
for(let value of range) {
alert(value); // 1,然后 2,然后 3,然后 4,然后 5
}
如果你想了解更多詳細(xì)內(nèi)容,請(qǐng)閱讀 generator 一章。
在常規(guī)的 generator 中,我們無(wú)法使用 await
。所有的值都必須按照 for..of
構(gòu)造的要求同步地出現(xiàn)。
如果我們想要異步地生成值該怎么辦?例如,對(duì)于來(lái)自網(wǎng)絡(luò)請(qǐng)求的值。
讓我們?cè)倩氐疆惒?generator,來(lái)使這個(gè)需求成為可能。
對(duì)于大多數(shù)的實(shí)際應(yīng)用程序,當(dāng)我們想創(chuàng)建一個(gè)異步生成一系列值的對(duì)象時(shí),我們都可以使用異步 generator。
語(yǔ)法很簡(jiǎn)單:在 function*
前面加上 async
。這即可使 generator 變?yōu)楫惒降摹?
然后使用 for await (...)
來(lái)遍歷它,像這樣:
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(在每個(gè) alert 之間有延遲)
}
})();
因?yàn)榇?generator 是異步的,所以我們可以在其內(nèi)部使用 await
,依賴(lài)于 promise
,執(zhí)行網(wǎng)絡(luò)請(qǐng)求等任務(wù)。
引擎蓋下的差異
如果你還記得我們?cè)谇懊嬲鹿?jié)中所講的關(guān)于 generator 的細(xì)節(jié)知識(shí),那你應(yīng)該知道,從技術(shù)上講,異步 generator 和常規(guī)的 generator 在內(nèi)部是有區(qū)別的。
對(duì)于異步 generator,
generator.next()
方法是異步的,它返回 promise。
在一個(gè)常規(guī)的 generator 中,我們使用
result = generator.next()
來(lái)獲得值。但在一個(gè)異步 generator 中,我們應(yīng)該添加await
關(guān)鍵字,像這樣:
result = await generator.next(); // result = {value: ..., done: true/false}
這就是為什么異步 generator 可以與
for await...of
一起工作。
常規(guī)的 generator 可用作 Symbol.iterator
以使迭代代碼更短。
與之類(lèi)似,異步 generator 可用作 Symbol.asyncIterator
來(lái)實(shí)現(xiàn)異步迭代。
例如,我們可以通過(guò)將同步的 Symbol.iterator
替換為異步的 Symbol.asyncIterator
,來(lái)使對(duì)象 range
異步地生成值,每秒生成一個(gè):
let range = {
from: 1,
to: 5,
// 這一行等價(jià)于 [Symbol.asyncIterator]: async function*() {
async *[Symbol.asyncIterator]() {
for(let value = this.from; value <= this.to; value++) {
// 在 value 之間暫停一會(huì)兒,等待一些東西
await new Promise(resolve => setTimeout(resolve, 1000));
yield value;
}
}
};
(async () => {
for await (let value of range) {
alert(value); // 1,然后 2,然后 3,然后 4,然后 5
}
})();
現(xiàn)在,value 之間的延遲為 1 秒。
請(qǐng)注意:
從技術(shù)上講,我們可以把
Symbol.iterator
和Symbol.asyncIterator
都添加到對(duì)象中,因此它既可以是同步的(for..of
)也可以是異步的(for await..of
)可迭代對(duì)象。
但是實(shí)際上,這將是一件很奇怪的事情。
到目前為止,我們已經(jīng)了解了一些基本示例,以加深理解。現(xiàn)在,我們來(lái)看一個(gè)實(shí)際的用例。
目前,有很多在線(xiàn)服務(wù)都是發(fā)送的分頁(yè)的數(shù)據(jù)(paginated data)。例如,當(dāng)我們需要一個(gè)用戶(hù)列表時(shí),一個(gè)請(qǐng)求只返回一個(gè)預(yù)設(shè)數(shù)量的用戶(hù)(例如 100 個(gè)用戶(hù))—— “一頁(yè)”,并提供了指向下一頁(yè)的 URL。
這種模式非常常見(jiàn)。不僅可用于獲取用戶(hù)列表,這種模式還可以用于任意東西。
例如,GitHub 允許使用相同的分頁(yè)提交(paginated fashion)的方式找回 commit:
https://api.github.com/repos/<repo>/commits
? 格式創(chuàng)建進(jìn)行 ?fetch
? 的網(wǎng)絡(luò)請(qǐng)求。Link
? header 中提供了指向下一頁(yè)的鏈接。對(duì)于我們的代碼,我們希望有一種更簡(jiǎn)單的獲取 commit 的方式。
讓我們創(chuàng)建一個(gè)函數(shù) fetchCommits(repo)
,用來(lái)在任何我們有需要的時(shí)候發(fā)出請(qǐng)求,來(lái)為我們獲取 commit。并且,該函數(shù)能夠關(guān)注到所有分頁(yè)內(nèi)容。對(duì)于我們來(lái)說(shuō),它將是一個(gè)簡(jiǎn)單的 for await..of
異步迭代。
因此,其用法將如下所示:
for await (let commit of fetchCommits("username/repository")) {
// 處理 commit
}
通過(guò)異步 generator,我們可以輕松實(shí)現(xiàn)上面所描述的函數(shù),如下所示:
async function* fetchCommits(repo) {
let url = `https://api.github.com/repos/${repo}/commits`;
while (url) {
const response = await fetch(url, { // (1)
headers: {'User-Agent': 'Our script'}, // github 需要任意的 user-agent header
});
const body = await response.json(); // (2) 響應(yīng)的是 JSON(array of commits)
// (3) 前往下一頁(yè)的 URL 在 header 中,提取它
let nextPage = response.headers.get('Link').match(/<(.*?)>; rel="next"/);
nextPage = nextPage?.[1];
url = nextPage;
for(let commit of body) { // (4) 一個(gè)接一個(gè)地 yield commit,直到最后一頁(yè)
yield commit;
}
}
}
關(guān)于其工作原理的進(jìn)一步解釋?zhuān)?
https://api.github.com/repos/<repo>/commits
?,并且下一頁(yè)的 URL 將在響應(yīng)的 ?Link
? header 中。fetch
? 方法允許我們提供授權(quán)和其他 header,如果需要 —— 這里 GitHub 需要的是 ?User-Agent
?。Link
? header 中獲取前往下一頁(yè)的 URL。它有一個(gè)特殊的格式,所以我們對(duì)它使用正則表達(dá)式(我們將在 正則表達(dá)式 一章中學(xué)習(xí)它)。https://api.github.com/repositories/93253246/commits?page=2
?。這是由 GitHub 自己生成的。while(url)
? 迭代,并發(fā)出下一個(gè)請(qǐng)求。這是一個(gè)使用示例(在控制臺(tái)中顯示 commit 的作者)
(async () => {
let count = 0;
for await (const commit of fetchCommits('javascript-tutorial/en.javascript.info')) {
console.log(commit.author.login);
if (++count == 100) { // 讓我們?cè)讷@取了 100 個(gè) commit 時(shí)停止
break;
}
}
})();
// 注意:如果你在外部沙箱中運(yùn)行它,你需要把上面的 fetchCommits 函數(shù)粘貼到這兒。
這就是我們想要的。
從外部看不到分頁(yè)請(qǐng)求(paginated requests)的內(nèi)部機(jī)制。對(duì)我們來(lái)說(shuō),它只是一個(gè)返回 commit 的異步 generator。
常規(guī)的 iterator 和 generator 可以很好地處理那些不需要花費(fèi)時(shí)間來(lái)生成的的數(shù)據(jù)。
當(dāng)我們期望異步地,有延遲地獲取數(shù)據(jù)時(shí),可以使用它們的異步版本,并且使用 for await..of
替代 for..of
。
異步 iterator 與常規(guī) iterator 在語(yǔ)法上的區(qū)別:
Iterable | 異步 Iterable | |
---|---|---|
提供 iterator 的對(duì)象方法 | Symbol.iterator
|
Symbol.asyncIterator
|
next() 返回的值是 |
{value:…, done: true/false}
|
resolve 成 {value:…, done: true/false} 的 Promise
|
異步 generator 與常規(guī) generator 在語(yǔ)法上的區(qū)別:
Generator | 異步 generator | |
---|---|---|
聲明方式 | function*
|
async function*
|
next() 返回的值是 |
{value:…, done: true/false}
|
resolve 成 {value:…, done: true/false} 的 Promise
|
在 Web 開(kāi)發(fā)中,我們經(jīng)常會(huì)遇到數(shù)據(jù)流,它們分段流動(dòng)(flows chunk-by-chunk)。例如,下載或上傳大文件。
我們可以使用異步 generator 來(lái)處理此類(lèi)數(shù)據(jù)。值得注意的是,在一些環(huán)境,例如瀏覽器環(huán)境下,還有另一個(gè)被稱(chēng)為 Streams 的 API,它提供了特殊的接口來(lái)處理此類(lèi)數(shù)據(jù)流,轉(zhuǎn)換數(shù)據(jù)并將數(shù)據(jù)從一個(gè)數(shù)據(jù)流傳遞到另一個(gè)數(shù)據(jù)流(例如,從一個(gè)地方下載并立即發(fā)送到其他地方)。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話(huà):173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: