您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)Node.js中的異步生成器和異步迭代是什么,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
生成器函數(shù)在 JavaScript 中的出現(xiàn)早于引入 async/await,這意味著在創(chuàng)建異步生成器(始終返回 Promise
且可以 await
的生成器)的同時,還引入了許多需要注意的事項。
今天,我們將研究異步生成器及其近親——異步迭代。
注意:盡管這些概念應(yīng)該適用于所有遵循現(xiàn)代規(guī)范的 javascript,但本文中的所有代碼都是針對 Node.js 10、12和 14 版開發(fā)和測試的。
看一下這個小程序:
// File: main.js const createGenerator = function*(){ yield 'a' yield 'b' yield 'c' } const main = () => { const generator = createGenerator() for (const item of generator) { console.log(item) } } main()
這段代碼定義了一個生成器函數(shù),用該函數(shù)創(chuàng)建了一個生成器對象,然后用 for ... of
循環(huán)遍歷該生成器對象。相當(dāng)標(biāo)準(zhǔn)的東西——盡管你絕不會在實際工作中用生成器來處理如此瑣碎的事情。如果你不熟悉生成器和 for ... of
循環(huán),請看《Javascript 生成器》 和 《ES6 的循環(huán)和可迭代對象的》 這兩篇文章。在使用異步生成器之前,你需要對生成器和 for ... of
循環(huán)有扎實的了解。
假設(shè)我們要在生成器函數(shù)中使用 await
,只要需要用 async
關(guān)鍵字聲明函數(shù),Node.js 就支持這個功能。如果你不熟悉異步函數(shù),那么請看 《在現(xiàn)代 JavaScript 中編寫異步任務(wù)》一文。
下面修改程序并在生成器中使用 await
。
// File: main.js const createGenerator = async function*(){ yield await new Promise((r) => r('a')) yield 'b' yield 'c' } const main = () => { const generator = createGenerator() for (const item of generator) { console.log(item) } } main()
同樣在實際工作中,你也不會這樣做——你可能會 await
來自第三方 API 或庫的函數(shù)。為了能讓大家輕松掌握,我們的例子盡量保持簡單。
如果嘗試運行上述程序,則會遇到問題:
$ node main.js /Users/alanstorm/Desktop/main.js:9 for (const item of generator) { ^ TypeError: generator is not iterable
JavaScript 告訴我們這個生成器是“不可迭代的”。乍一看,似乎使生成器函數(shù)異步也意味著它生成的生成器是不可迭代的。這有點令人困惑,因為生成器的目的是生成“以編程方式”可迭代的對象。
接下來搞清楚到底發(fā)生了什么。
如果你看了 Javascript 生成器這篇文章 ,那么就應(yīng)該知道,如果對象定義了 Symbol.iterator
方法,并且該方法返回,則它在 javascript 中是一個實現(xiàn)了迭代器協(xié)議的可迭代對象。當(dāng)對象具有 next
方法時,該對象將實現(xiàn)迭代器協(xié)議,并且該 next
方法返回帶有 value
屬性,done
屬性之一或同時帶有 value
和 done
屬性的對象。
如果用下面這段代碼比較異步生成器函數(shù)與常規(guī)生成器函數(shù)返回的生成器對象:
// File: test-program.js const createGenerator = function*(){ yield 'a' yield 'b' yield 'c' } const createAsyncGenerator = async function*(){ yield await new Promise((r) => r('a')) yield 'b' yield 'c' } const main = () => { const generator = createGenerator() const asyncGenerator = createAsyncGenerator() console.log('generator:',generator[Symbol.iterator]) console.log('asyncGenerator',asyncGenerator[Symbol.iterator]) } main()
則會看到,前者沒有 Symbol.iterator
方法,而后者有。
$ node test-program.js generator: [Function: [Symbol.iterator]] asyncGenerator undefined
這兩個生成器對象都有一個 next
方法。如果修改測試代碼來調(diào)用這個 next
方法:
// File: test-program.js /* ... */ const main = () => { const generator = createGenerator() const asyncGenerator = createAsyncGenerator() console.log('generator:',generator.next()) console.log('asyncGenerator',asyncGenerator.next()) } main()
則會看到另一個問題:
$ node test-program.js generator: { value: 'a', done: false } asyncGenerator Promise { <pending> }
為了使對象可迭代,next
方法需要返回帶有 value
和 done
屬性的對象。一個 async
函數(shù)將總是返回一個 Promise
對象。這個特性會帶到用異步函數(shù)創(chuàng)建的生成器上——這些異步生成器始終會 yield
一個 Promise
對象。
這種行為使得 async
函數(shù)的生成器無法實現(xiàn) javascript 迭代協(xié)議。
幸運的是有辦法解決這個矛盾。如果看一看 async
生成器返回的構(gòu)造函數(shù)或類
// File: test-program.js /* ... */ const main = () => { const generator = createGenerator() const asyncGenerator = createAsyncGenerator() console.log('asyncGenerator',asyncGenerator) }
可以看到它是一個對象,其類型或類或構(gòu)造函數(shù)是 AsyncGenerator
而不是 Generator
:
asyncGenerator Object [AsyncGenerator] {}
盡管該對象有可能不是可迭代的,但它是異步可迭代的。
要想使對象能夠異步迭代,它必須實現(xiàn)一個 Symbol.asyncIterator
方法。這個方法必須返回一個對象,該對象實現(xiàn)了異步版本的迭代器協(xié)議。也就是說,對象必須具有返回 Promise
的 next
方法,并且這個 promise 必須最終解析為帶有 done
和 value
屬性的對象。
一個 AsyncGenerator
對象滿足所有這些條件。
這就留下了一個問題——我們怎樣才能遍歷一個不可迭代但可以異步迭代的對象?
只用生成器的 next
方法就可以手動迭代異步可迭代對象。 (注意,這里的 main
函數(shù)現(xiàn)在是 async main
——這樣能夠使我們在函數(shù)內(nèi)部使用 await
)
// File: main.js const createAsyncGenerator = async function*(){ yield await new Promise((r) => r('a')) yield 'b' yield 'c' } const main = async () => { const asyncGenerator = createAsyncGenerator() let result = {done:false} while(!result.done) { result = await asyncGenerator.next() if(result.done) { continue; } console.log(result.value) } } main()
但是,這不是最直接的循環(huán)機制。我既不喜歡 while
的循環(huán)條件,也不想手動檢查 result.done
。另外, result.done
變量必須同時存在于內(nèi)部和外部塊的作用域內(nèi)。
幸運的是大多數(shù)(也許是所有?)支持異步迭代器的 javascript 實現(xiàn)也都支持特殊的 for await ... of
循環(huán)語法。例如:
const createAsyncGenerator = async function*(){ yield await new Promise((r) => r('a')) yield 'b' yield 'c' } const main = async () => { const asyncGenerator = createAsyncGenerator() for await(const item of asyncGenerator) { console.log(item) } } main()
如果運行上述代碼,則會看到異步生成器與可迭代對象已被成功循環(huán),并且在循環(huán)體中得到了 Promise
的完全解析值。
$ node main.js a b c
這個 for await ... of
循環(huán)更喜歡實現(xiàn)了異步迭代器協(xié)議的對象。但是你可以用它遍歷任何一種可迭代對象。
for await(const item of [1,2,3]) { console.log(item) }
當(dāng)你使用 for await
時,Node.js 將會首先在對象上尋找 Symbol.asyncIterator
方法。如果找不到,它將回退到使用 Symbol.iterator
的方法。
與 await
一樣,for await
循環(huán)會將非線性代碼執(zhí)行引入程序中。也就是說,你的代碼將會以和編寫的代碼不同的順序運行。
當(dāng)你的程序第一次遇到 for await
循環(huán)時,它將在你的對象上調(diào)用 next
。
該對象將 yield
一個 promise,然后代碼的執(zhí)行將會離開你的 async
函數(shù),并且你的程序?qū)⒗^續(xù)在該函數(shù)之外執(zhí)行。
一旦你的 promise 得到解決,代碼執(zhí)行將會使用這個值返回到循環(huán)體。
當(dāng)循環(huán)結(jié)束并進行下一個行程時,Node.js 將在對象上調(diào)用 next
。該調(diào)用會產(chǎn)生另一個 promise,代碼執(zhí)行將會再次離開你的函數(shù)。重復(fù)這種模式,直到 Promise 解析為 done
為 true
的對象,然后在 for await
循環(huán)之后繼續(xù)執(zhí)行代碼。
下面的例子可以說明一點:
let count = 0 const getCount = () => { count++ return `${count}. ` } const createAsyncGenerator = async function*() { console.log(getCount() + 'entering createAsyncGenerator') console.log(getCount() + 'about to yield a') yield await new Promise((r)=>r('a')) console.log(getCount() + 're-entering createAsyncGenerator') console.log(getCount() + 'about to yield b') yield 'b' console.log(getCount() + 're-entering createAsyncGenerator') console.log(getCount() + 'about to yield c') yield 'c' console.log(getCount() + 're-entering createAsyncGenerator') console.log(getCount() + 'exiting createAsyncGenerator') } const main = async () => { console.log(getCount() + 'entering main') const asyncGenerator = createAsyncGenerator() console.log(getCount() + 'starting for await loop') for await(const item of asyncGenerator) { console.log(getCount() + 'entering for await loop') console.log(getCount() + item) console.log(getCount() + 'exiting for await loop') } console.log(getCount() + 'done with for await loop') console.log(getCount() + 'leaving main') } console.log(getCount() + 'before calling main') main() console.log(getCount() + 'after calling main')
這段代碼你用了編號的日志記錄語句,可讓你跟蹤其執(zhí)行情況。作為練習(xí),你需要自己運行程序然后查看執(zhí)行結(jié)果是怎樣的。
如果你不知道它的工作方式,就會使程序的執(zhí)行產(chǎn)生混亂,但異步迭代的確是一項強大的技術(shù)。
關(guān)于Node.js中的異步生成器和異步迭代是什么就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。