日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

“睡服”面试官系列第十九篇之async函数(建议收藏学习)

發(fā)布時(shí)間:2023/12/10 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 “睡服”面试官系列第十九篇之async函数(建议收藏学习) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

目錄

?

1. 含義?

2. 基本用法

3. 語法

3.1返回 Promise 對(duì)象

3.2Promise 對(duì)象的狀態(tài)變化

3.3await 命令

3.4錯(cuò)誤處理

3.5使用注意點(diǎn)

4. async 函數(shù)的實(shí)現(xiàn)原理

5. 與其他異步處理方法的比較

6. 實(shí)例:按順序完成異步操作

7. 異步遍歷器

7.1異步遍歷的接口

7.2for await...of

7.3異步 Generator 函數(shù)

7.4yield* 語句

總結(jié)

“睡服“面試官系列之各系列目錄匯總(建議學(xué)習(xí)收藏)


1. 含義?

ES2017 標(biāo)準(zhǔn)引入了 async 函數(shù),使得異步操作變得更加方便。
async 函數(shù)是什么?一句話,它就是 Generator 函數(shù)的語法糖。
前文有一個(gè) Generator 函數(shù),依次讀取兩個(gè)文件

const fs = require('fs') const readFile = function (fileName) { return new Promise(function (resolve, reject) { fs.readFile(fileName, function(error, data) { if (error) return reject(error); resolve(data); }); }); }; const gen = function* () { const f1 = yield readFile('/etc/fstab'); const f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); }

寫成 async 函數(shù),就是下面這樣

const asyncReadFile = async function () { const f1 = await readFile('/etc/fstab'); const f2 = await readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };

一比較就會(huì)發(fā)現(xiàn), async 函數(shù)就是將 Generator 函數(shù)的星號(hào)( * )替換成 async ,將 yield 替換成 await ,僅此而已。
async 函數(shù)對(duì) Generator 函數(shù)的改進(jìn),體現(xiàn)在以下四點(diǎn)

(1)內(nèi)置執(zhí)行器。
Generator 函數(shù)的執(zhí)行必須靠執(zhí)行器,所以才有了 co 模塊,而 async 函數(shù)自帶執(zhí)行器。也就是說, async 函數(shù)的執(zhí)行,與普通函數(shù)一模一樣,只要一
行。
asyncReadFile();
上面的代碼調(diào)用了 asyncReadFile 函數(shù),然后它就會(huì)自動(dòng)執(zhí)行,輸出最后結(jié)果。這完全不像 Generator 函數(shù),需要調(diào)用 next 方法,或者用 co 模塊,才能
真正執(zhí)行,得到最后結(jié)果。
(2)更好的語義。
async 和 await ,比起星號(hào)和 yield ,語義更清楚了。 async 表示函數(shù)里有異步操作, await 表示緊跟在后面的表達(dá)式需要等待結(jié)果。
(3)更廣的適用性。
co 模塊約定, yield 命令后面只能是 Thunk 函數(shù)或 Promise 對(duì)象,而 async 函數(shù)的 await 命令后面,可以是 Promise 對(duì)象和原始類型的值(數(shù)值、字
符串和布爾值,但這時(shí)等同于同步操作)。
(4)返回值是 Promise。
async 函數(shù)的返回值是 Promise 對(duì)象,這比 Generator 函數(shù)的返回值是 Iterator 對(duì)象方便多了。你可以用 then 方法指定下一步的操作。
進(jìn)一步說, async 函數(shù)完全可以看作多個(gè)異步操作,包裝成的一個(gè) Promise 對(duì)象,而 await 命令就是內(nèi)部 then 命令的語法糖。

2. 基本用法

async 函數(shù)返回一個(gè) Promise 對(duì)象,可以使用 then 方法添加回調(diào)函數(shù)。當(dāng)函數(shù)執(zhí)行的時(shí)候,一旦遇到 await 就會(huì)先返回,等到異步操作完成,再接著執(zhí)
行函數(shù)體內(nèi)后面的語句。
下面是一個(gè)例子

async function getStockPriceByName(name) { const symbol = await getStockSymbol(name); const stockPrice = await getStockPrice(symbol); return stockPrice; } getStockPriceByName('goog').then(function (result) { console.log(result); });

上面代碼是一個(gè)獲取股票報(bào)價(jià)的函數(shù),函數(shù)前面的 async 關(guān)鍵字,表明該函數(shù)內(nèi)部有異步操作。調(diào)用該函數(shù)時(shí),會(huì)立即返回一個(gè) Promise 對(duì)象。
下面是另一個(gè)例子,指定多少毫秒后輸出一個(gè)值

function timeout(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } async function asyncPrint(value, ms) { await timeout(ms); console.log(value); } asyncPrint('hello world', 50);

上面代碼指定 50 毫秒以后,輸出 hello world 。
由于 async 函數(shù)返回的是 Promise 對(duì)象,可以作為 await 命令的參數(shù)。所以,上面的例子也可以寫成下面的形式。

async function timeout(ms) { await new Promise((resolve) => { setTimeout(resolve, ms); }); } async function asyncPrint(value, ms) { await timeout(ms); console.log(value); } asyncPrint('hello world', 50);

async 函數(shù)有多種使用形式。

// 函數(shù)聲明 async function foo() {} // 函數(shù)表達(dá)式 const foo = async function () {}; // 對(duì)象的方法 let obj = { async foo() {} }; obj.foo().then(...) // Class 的方法 class Storage { constructor() { this.cachePromise = caches.open('avatars'); } async getAvatar(name) { const cache = await this.cachePromise; return cache.match(`/avatars/${name}.jpg`); } } const storage = new Storage(); storage.getAvatar('jake').then(…); // 箭頭函數(shù) const foo = async () => {};

3. 語法

async 函數(shù)的語法規(guī)則總體上比較簡(jiǎn)單,難點(diǎn)是錯(cuò)誤處理機(jī)制

3.1返回 Promise 對(duì)象

async 函數(shù)返回一個(gè) Promise 對(duì)象。
async 函數(shù)內(nèi)部 return 語句返回的值,會(huì)成為 then 方法回調(diào)函數(shù)的參數(shù)

async function f() { return 'hello world'; }f().then(v => console.log(v)) // "hello world

上面代碼中,函數(shù) f 內(nèi)部 return 命令返回的值,會(huì)被 then 方法回調(diào)函數(shù)接收到。
async 函數(shù)內(nèi)部拋出錯(cuò)誤,會(huì)導(dǎo)致返回的 Promise 對(duì)象變?yōu)?reject 狀態(tài)。拋出的錯(cuò)誤對(duì)象會(huì)被 catch 方法回調(diào)函數(shù)接收到。

async function f() { throw new Error('出錯(cuò)了'); } f().then( v => console.log(v), e => console.log(e) ) // Error: 出錯(cuò)了

3.2Promise 對(duì)象的狀態(tài)變化

async 函數(shù)返回的 Promise 對(duì)象,必須等到內(nèi)部所有 await 命令后面的 Promise 對(duì)象執(zhí)行完,才會(huì)發(fā)生狀態(tài)改變,除非遇到 return 語句或者拋出錯(cuò)
誤。也就是說,只有 async 函數(shù)內(nèi)部的異步操作執(zhí)行完,才會(huì)執(zhí)行 then 方法指定的回調(diào)函數(shù)。
下面是一個(gè)例子

async function getTitle(url) { let response = await fetch(url); let html = await response.text(); return html.match(/<title>([\s\S]+)<\/title>/i)[1]; } getTitle('https://tc39.github.io/ecma262/').then(console.log) // "ECMAScript 2017 Language Specification"

上面代碼中,函數(shù) getTitle 內(nèi)部有三個(gè)操作:抓取網(wǎng)頁(yè)、取出文本、匹配頁(yè)面標(biāo)題。只有這三個(gè)操作全部完成,才會(huì)執(zhí)行 then 方法里面的
console.log

3.3await 命令

正常情況下, await 命令后面是一個(gè) Promise 對(duì)象。如果不是,會(huì)被轉(zhuǎn)成一個(gè)立即 resolve 的 Promise 對(duì)象。

async function f() { return await 123; } f().then(v => console.log(v)) // 123

上面代碼中, await 命令的參數(shù)是數(shù)值 123 ,它被轉(zhuǎn)成 Promise 對(duì)象,并立即 resolve 。
await 命令后面的 Promise 對(duì)象如果變?yōu)?reject 狀態(tài),則 reject 的參數(shù)會(huì)被 catch 方法的回調(diào)函數(shù)接收到

async function f() { await Promise.reject('出錯(cuò)了'); } f() .then(v => console.log(v)) .catch(e => console.log(e)) // 出錯(cuò)了

注意,上面代碼中, await 語句前面沒有 return ,但是 reject 方法的參數(shù)依然傳入了 catch 方法的回調(diào)函數(shù)。這里如果在 await 前面加上 return ,效果
是一樣的。
只要一個(gè) await 語句后面的 Promise 變?yōu)?reject ,那么整個(gè) async 函數(shù)都會(huì)中斷執(zhí)行。

async function f() { await Promise.reject('出錯(cuò)了'); await Promise.resolve('hello world'); // 不會(huì)執(zhí)行 }

上面代碼中,第二個(gè) await 語句是不會(huì)執(zhí)行的,因?yàn)榈谝粋€(gè) await 語句狀態(tài)變成了 reject 。
有時(shí),我們希望即使前一個(gè)異步操作失敗,也不要中斷后面的異步操作。這時(shí)可以將第一個(gè) await 放在 try...catch 結(jié)構(gòu)里面,這樣不管這個(gè)異步操作是
否成功,第二個(gè) await 都會(huì)執(zhí)行。

async function f() { try { await Promise.reject('出錯(cuò)了'); } catch(e) { } return await Promise.resolve('hello world'); } f() .then(v => console.log(v)) // hello world

另一種方法是 await 后面的 Promise 對(duì)象再跟一個(gè) catch 方法,處理前面可能出現(xiàn)的錯(cuò)誤。

async function f() { await Promise.reject('出錯(cuò)了') .catch(e => console.log(e)); return await Promise.resolve('hello world'); } f() .then(v => console.log(v)) // 出錯(cuò)了 // hello world

3.4錯(cuò)誤處理

如果 await 后面的異步操作出錯(cuò),那么等同于 async 函數(shù)返回的 Promise 對(duì)象被 reject

async function f() { await new Promise(function (resolve, reject) { throw new Error('出錯(cuò)了'); }); } f() .then(v => console.log(v)) .catch(e => console.log(e)) // Error:出錯(cuò)了

上面代碼中, async 函數(shù) f 執(zhí)行后, await 后面的 Promise 對(duì)象會(huì)拋出一個(gè)錯(cuò)誤對(duì)象,導(dǎo)致 catch 方法的回調(diào)函數(shù)被調(diào)用,它的參數(shù)就是拋出的錯(cuò)誤對(duì)
象。具體的執(zhí)行機(jī)制,可以參考后文的“async 函數(shù)的實(shí)現(xiàn)原理”。
防止出錯(cuò)的方法,也是將其放在 try...catch 代碼塊之中

async function f() { try { await new Promise(function (resolve, reject) { throw new Error('出錯(cuò)了'); }); } catch(e) { } return await('hello world'); }

如果有多個(gè) await 命令,可以統(tǒng)一放在 try...catch 結(jié)構(gòu)中

async function main() { try { const val1 = await firstStep(); const val2 = await secondStep(val1); const val3 = await thirdStep(val1, val2); console.log('Final: ', val3); } catch (err) { console.error(err); } }

下面的例子使用 try...catch 結(jié)構(gòu),實(shí)現(xiàn)多次重復(fù)嘗試

const superagent = require('superagent'); const NUM_RETRIES = 3; async function test() { let i; for (i = 0; i < NUM_RETRIES; ++i) { try { await superagent.get('http://google.com/this-throws-an-error'); break; } catch(err) {} } console.log(i); // 3 } test();

上面代碼中,如果 await 操作成功,就會(huì)使用 break 語句退出循環(huán);如果失敗,會(huì)被 catch 語句捕捉,然后進(jìn)入下一輪循環(huán)。

3.5使用注意點(diǎn)

第一點(diǎn),前面已經(jīng)說過, await 命令后面的 Promise 對(duì)象,運(yùn)行結(jié)果可能是 rejected ,所以最好把 await 命令放在 try...catch 代碼塊中。

async function myFunction() { try { await somethingThatReturnsAPromise(); } catch (err) { console.log(err); } } // 另一種寫法 async function myFunction() { await somethingThatReturnsAPromise() .catch(function (err) { console.log(err); }); }

第二點(diǎn),多個(gè) await 命令后面的異步操作,如果不存在繼發(fā)關(guān)系,最好讓它們同時(shí)觸發(fā)。

let foo = await getFoo(); let bar = await getBar();

上面代碼中, getFoo 和 getBar 是兩個(gè)獨(dú)立的異步操作(即互不依賴),被寫成繼發(fā)關(guān)系。這樣比較耗時(shí),因?yàn)橹挥?getFoo 完成以后,才會(huì)執(zhí)行
getBar ,完全可以讓它們同時(shí)觸發(fā)。

// 寫法一 let [foo, bar] = await Promise.all([getFoo(), getBar()]); // 寫法二 let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise; let bar = await barPromise;

上面兩種寫法, getFoo 和 getBar 都是同時(shí)觸發(fā),這樣就會(huì)縮短程序的執(zhí)行時(shí)間。
第三點(diǎn), await 命令只能用在 async 函數(shù)之中,如果用在普通函數(shù),就會(huì)報(bào)錯(cuò)。

async function dbFuc(db) { let docs = [{}, {}, {}]; // 報(bào)錯(cuò) docs.forEach(function (doc) { await db.post(doc); }); }

上面代碼會(huì)報(bào)錯(cuò),因?yàn)?await 用在普通函數(shù)之中了。但是,如果將 forEach 方法的參數(shù)改成 async 函數(shù),也有問題

function dbFuc(db) { //這里不需要 async let docs = [{}, {}, {}]; // 可能得到錯(cuò)誤結(jié)果 docs.forEach(async function (doc) { await db.post(doc); }); }

上面代碼可能不會(huì)正常工作,原因是這時(shí)三個(gè) db.post 操作將是并發(fā)執(zhí)行,也就是同時(shí)執(zhí)行,而不是繼發(fā)執(zhí)行。正確的寫法是采用 for 循環(huán)

async function dbFuc(db) { let docs = [{}, {}, {}]; for (let doc of docs) { await db.post(doc); } }

如果確實(shí)希望多個(gè)請(qǐng)求并發(fā)執(zhí)行,可以使用 Promise.all 方法。當(dāng)三個(gè)請(qǐng)求都會(huì) resolved 時(shí),下面兩種寫法效果相同。

async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = await Promise.all(promises); console.log(results); } // 或者使用下面的寫法 async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = []; for (let promise of promises) { results.push(await promise); } console.log(results); }

目前, @std/esm 模塊加載器支持頂層 await ,即 await 命令可以不放在 async 函數(shù)里面,直接使用

// async 函數(shù)的寫法 const start = async () => { const res = await fetch('google.com'); return res.text(); }; start().then(console.log); // 頂層 await 的寫法 const res = await fetch('google.com'); console.log(await res.text());

上面代碼中,第二種寫法的腳本必須使用 @std/esm 加載器,才會(huì)生效。

4. async 函數(shù)的實(shí)現(xiàn)原理

async 函數(shù)的實(shí)現(xiàn)原理,就是將 Generator 函數(shù)和自動(dòng)執(zhí)行器,包裝在一個(gè)函數(shù)里。

async function fn(args) { // ... } // 等同于 function fn(args) { return spawn(function* () { // ... }); }

所有的 async 函數(shù)都可以寫成上面的第二種形式,其中的 spawn 函數(shù)就是自動(dòng)執(zhí)行器。
下面給出 spawn 函數(shù)的實(shí)現(xiàn),基本就是前文自動(dòng)執(zhí)行器的翻版

function spawn(genF) { return new Promise(function(resolve, reject) { const gen = genF(); function step(nextF) { let next; try { next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v); }); }, function(e) { step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); }

5. 與其他異步處理方法的比較

我們通過一個(gè)例子,來看 async 函數(shù)與 Promise、Generator 函數(shù)的比較。
假定某個(gè) DOM 元素上面,部署了一系列的動(dòng)畫,前一個(gè)動(dòng)畫結(jié)束,才能開始后一個(gè)。如果當(dāng)中有一個(gè)動(dòng)畫出錯(cuò),就不再往下執(zhí)行,返回上一個(gè)成功執(zhí)行
的動(dòng)畫的返回值。
首先是 Promise 的寫法

首先是 Promise 的寫法。 function chainAnimationsPromise(elem, animations) { // 變量ret用來保存上一個(gè)動(dòng)畫的返回值 let ret = null; // 新建一個(gè)空的Promise let p = Promise.resolve(); // 使用then方法,添加所有動(dòng)畫 for(let anim of animations) { p = p.then(function(val) { ret = val; return anim(elem); }); } // 返回一個(gè)部署了錯(cuò)誤捕捉機(jī)制的Promise return p.catch(function(e) { /* 忽略錯(cuò)誤,繼續(xù)執(zhí)行 */ }).then(function() { return ret; }); }

雖然 Promise 的寫法比回調(diào)函數(shù)的寫法大大改進(jìn),但是一眼看上去,代碼完全都是 Promise 的 API( then 、 catch 等等),操作本身的語義反而不容
易看出來。
接著是 Generator 函數(shù)的寫法。

function chainAnimationsGenerator(elem, animations) { return spawn(function*() { let ret = null; try { for(let anim of animations) { ret = yield anim(elem); } } catch(e) { /* 忽略錯(cuò)誤,繼續(xù)執(zhí)行 */ } return ret; }); }

上面代碼使用 Generator 函數(shù)遍歷了每個(gè)動(dòng)畫,語義比 Promise 寫法更清晰,用戶定義的操作全部都出現(xiàn)在 spawn 函數(shù)的內(nèi)部。這個(gè)寫法的問題在于,
必須有一個(gè)任務(wù)運(yùn)行器,自動(dòng)執(zhí)行 Generator 函數(shù),上面代碼的 spawn 函數(shù)就是自動(dòng)執(zhí)行器,它返回一個(gè) Promise 對(duì)象,而且必須保證 yield 語句后面
的表達(dá)式,必須返回一個(gè) Promise。
最后是 async 函數(shù)的寫法

async function chainAnimationsAsync(elem, animations) { let ret = null; try { for(let anim of animations) { ret = await anim(elem); } } catch(e) { /* 忽略錯(cuò)誤,繼續(xù)執(zhí)行 */ } return ret; }

可以看到 Async 函數(shù)的實(shí)現(xiàn)最簡(jiǎn)潔,最符合語義,幾乎沒有語義不相關(guān)的代碼。它將 Generator 寫法中的自動(dòng)執(zhí)行器,改在語言層面提供,不暴露給用
戶,因此代碼量最少。如果使用 Generator 寫法,自動(dòng)執(zhí)行器需要用戶自己提供

6. 實(shí)例:按順序完成異步操作

實(shí)際開發(fā)中,經(jīng)常遇到一組異步操作,需要按照順序完成。比如,依次遠(yuǎn)程讀取一組 URL,然后按照讀取的順序輸出結(jié)果。
Promise 的寫法如下

function logInOrder(urls) { // 遠(yuǎn)程讀取所有URL const textPromises = urls.map(url => { return fetch(url).then(response => response.text()); }); // 按次序輸出 textPromises.reduce((chain, textPromise) => { return chain.then(() => textPromise) .then(text => console.log(text)); }, Promise.resolve()); }

上面代碼使用 fetch 方法,同時(shí)遠(yuǎn)程讀取一組 URL。每個(gè) fetch 操作都返回一個(gè) Promise 對(duì)象,放入 textPromises 數(shù)組。然后, reduce 方法依次處理
每個(gè) Promise 對(duì)象,然后使用 then ,將所有 Promise 對(duì)象連起來,因此就可以依次輸出結(jié)果。
這種寫法不太直觀,可讀性比較差。下面是 async 函數(shù)實(shí)現(xiàn)

async function logInOrder(urls) { for (const url of urls) { const response = await fetch(url); console.log(await response.text()); } }

上面代碼確實(shí)大大簡(jiǎn)化,問題是所有遠(yuǎn)程操作都是繼發(fā)。只有前一個(gè) URL 返回結(jié)果,才會(huì)去讀取下一個(gè) URL,這樣做效率很差,非常浪費(fèi)時(shí)間。我們需
要的是并發(fā)發(fā)出遠(yuǎn)程請(qǐng)求。

async function logInOrder(urls) { // 并發(fā)讀取遠(yuǎn)程URL const textPromises = urls.map(async url => { const response = await fetch(url); return response.text(); }); // 按次序輸出 for (const textPromise of textPromises) { console.log(await textPromise); } }

上面代碼中,雖然 map 方法的參數(shù)是 async 函數(shù),但它是并發(fā)執(zhí)行的,因?yàn)橹挥?async 函數(shù)內(nèi)部是繼發(fā)執(zhí)行,外部不受影響。后面的 for..of 循環(huán)內(nèi)部使
用了 await ,因此實(shí)現(xiàn)了按順序輸出

7. 異步遍歷器

Iterator 接口是一種數(shù)據(jù)遍歷的協(xié)議,只要調(diào)用遍歷器對(duì)象的 next 方法,就會(huì)得到一個(gè)對(duì)象,表示當(dāng)前遍歷指針?biāo)诘哪莻€(gè)位置的
信息。 next 方法返回的對(duì)象的結(jié)構(gòu)是 {value, done} ,其中 value 表示當(dāng)前的數(shù)據(jù)的值, done 是一個(gè)布爾值,表示遍歷是否結(jié)束。
這里隱含著一個(gè)規(guī)定, next 方法必須是同步的,只要調(diào)用就必須立刻返回值。也就是說,一旦執(zhí)行 next 方法,就必須同步地得到 value 和 done 這兩個(gè)屬
性。如果遍歷指針正好指向同步操作,當(dāng)然沒有問題,但對(duì)于異步操作,就不太合適了。目前的解決方法是,Generator 函數(shù)里面的異步操作,返回一個(gè)
Thunk 函數(shù)或者 Promise 對(duì)象,即 value 屬性是一個(gè) Thunk 函數(shù)或者 Promise 對(duì)象,等待以后返回真正的值,而 done 屬性則還是同步產(chǎn)生的。
目前,有一個(gè)提案,為異步操作提供原生的遍歷器接口,即 value 和 done 這兩個(gè)屬性都是異步產(chǎn)生,這稱為”異步遍歷器“(Async Iterator)

7.1異步遍歷的接口

異步遍歷器的最大的語法特點(diǎn),就是調(diào)用遍歷器的 next 方法,返回的是一個(gè) Promise 對(duì)象。

asyncIterator .next() .then( ({ value, done }) => /* ... */ );

上面代碼中, asyncIterator 是一個(gè)異步遍歷器,調(diào)用 next 方法以后,返回一個(gè) Promise 對(duì)象。因此,可以使用 then 方法指定,這個(gè) Promise 對(duì)象的
狀態(tài)變?yōu)?resolve 以后的回調(diào)函數(shù)。回調(diào)函數(shù)的參數(shù),則是一個(gè)具有 value 和 done 兩個(gè)屬性的對(duì)象,這個(gè)跟同步遍歷器是一樣的。
我們知道,一個(gè)對(duì)象的同步遍歷器的接口,部署在 Symbol.iterator 屬性上面。同樣地,對(duì)象的異步遍歷器接口,部署在 Symbol.asyncIterator 屬性上
面。不管是什么樣的對(duì)象,只要它的 Symbol.asyncIterator 屬性有值,就表示應(yīng)該對(duì)它進(jìn)行異步遍歷。
下面是一個(gè)異步遍歷器的例子。

const asyncIterable = createAsyncIterable(['a', 'b']); const asyncIterator = asyncIterable[Symbol.asyncIterator](); asyncIterator .next() .then(iterResult1 => { console.log(iterResult1); // { value: 'a', done: false } return asyncIterator.next(); }) .then(iterResult2 => { console.log(iterResult2); // { value: 'b', done: false } return asyncIterator.next(); }) .then(iterResult3 => { console.log(iterResult3); // { value: undefined, done: true }

上面代碼中,異步遍歷器其實(shí)返回了兩次值。第一次調(diào)用的時(shí)候,返回一個(gè) Promise 對(duì)象;等到 Promise 對(duì)象 resolve 了,再返回一個(gè)表示當(dāng)前數(shù)據(jù)成
員信息的對(duì)象。這就是說,異步遍歷器與同步遍歷器最終行為是一致的,只是會(huì)先返回 Promise 對(duì)象,作為中介。
由于異步遍歷器的 next 方法,返回的是一個(gè) Promise 對(duì)象。因此,可以把它放在 await 命令后面。

async function f() { const asyncIterable = createAsyncIterable(['a', 'b']); const asyncIterator = asyncIterable[Symbol.asyncIterator](); console.log(await asyncIterator.next()); // { value: 'a', done: false } console.log(await asyncIterator.next()); // { value: 'b', done: false } console.log(await asyncIterator.next()); // { value: undefined, done: true } }

上面代碼中, next 方法用 await 處理以后,就不必使用 then 方法了。整個(gè)流程已經(jīng)很接近同步處理了。
注意,異步遍歷器的 next 方法是可以連續(xù)調(diào)用的,不必等到上一步產(chǎn)生的 Promise 對(duì)象 resolve 以后再調(diào)用。這種情況下, next 方法會(huì)累積起來,自動(dòng)
按照每一步的順序運(yùn)行下去。下面是一個(gè)例子,把所有的 next 方法放在 Promise.all 方法里面。

const asyncGenObj = createAsyncIterable(['a', 'b']); const [{value: v1}, {value: v2}] = await Promise.all([ asyncGenObj.next(), asyncGenObj.next() ]); console.log(v1, v2); // a b


另一種用法是一次性調(diào)用所有的 next 方法,然后 await 最后一步操作

const writer = openFile('someFile.txt'); writer.next('hello'); writer.next('world'); await writer.return();

7.2for await...of

?for...of 循環(huán)用于遍歷同步的 Iterator 接口。新引入的 for await...of 循環(huán),則是用于遍歷異步的 Iterator 接口。

async function f() { for await (const x of createAsyncIterable(['a', 'b'])) { console.log(x); } } // a // b

上面代碼中, createAsyncIterable() 返回一個(gè)異步遍歷器, for...of 循環(huán)自動(dòng)調(diào)用這個(gè)遍歷器的 next 方法,會(huì)得到一個(gè) Promise 對(duì)象。 await 用來
處理這個(gè) Promise 對(duì)象,一旦 resolve ,就把得到的值( x )傳入 for...of 的循環(huán)體。
for await...of 循環(huán)的一個(gè)用途,是部署了 asyncIterable 操作的異步接口,可以直接放入這個(gè)循環(huán)。

let body = ''; async function f() { for await(const data of req) body += data; const parsed = JSON.parse(body); console.log('got', parsed); }

上面代碼中, req 是一個(gè) asyncIterable 對(duì)象,用來異步讀取數(shù)據(jù)。可以看到,使用 for await...of 循環(huán)以后,代碼會(huì)非常簡(jiǎn)潔。
如果 next 方法返回的 Promise 對(duì)象被 reject , for await...of 就會(huì)報(bào)錯(cuò),要用 try...catch 捕捉

async function () { try { for await (const x of createRejectingIterable()) { console.log(x); } } catch (e) { console.error(e); } }

注意, for await...of 循環(huán)也可以用于同步遍歷器

(async function () { for await (const x of ['a', 'b']) { console.log(x); } })(); // a //

7.3異步 Generator 函數(shù)

就像 Generator 函數(shù)返回一個(gè)同步遍歷器對(duì)象一樣,異步 Generator 函數(shù)的作用,是返回一個(gè)異步遍歷器對(duì)象。
在語法上,異步 Generator 函數(shù)就是 async 函數(shù)與 Generator 函數(shù)的結(jié)合。

async function* gen() { yield 'hello'; } const genObj = gen(); genObj.next().then(x => console.log(x)); // { value: 'hello', done: false }

上面代碼中, gen 是一個(gè)異步 Generator 函數(shù),執(zhí)行后返回一個(gè)異步 Iterator 對(duì)象。對(duì)該對(duì)象調(diào)用 next 方法,返回一個(gè) Promise 對(duì)象。
異步遍歷器的設(shè)計(jì)目的之一,就是 Generator 函數(shù)處理同步操作和異步操作時(shí),能夠使用同一套接口

// 同步 Generator 函數(shù) function* map(iterable, func) { const iter = iterable[Symbol.iterator](); while (true) { const {value, done} = iter.next(); if (done) break; yield func(value); } } // 異步 Generator 函數(shù) async function* map(iterable, func) { const iter = iterable[Symbol.asyncIterator](); while (true) { const {value, done} = await iter.next(); if (done) break; yield func(value); } }

上面代碼中,可以看到有了異步遍歷器以后,同步 Generator 函數(shù)和異步 Generator 函數(shù)的寫法基本上是一致的。
下面是另一個(gè)異步 Generator 函數(shù)的例子

async function* readLines(path) { let file = await fileOpen(path); try { while (!file.EOF) { yield await file.readLine(); } } finally { await file.close(); } }

上面代碼中,異步操作前面使用 await 關(guān)鍵字標(biāo)明,即 await 后面的操作,應(yīng)該返回 Promise 對(duì)象。凡是使用 yield 關(guān)鍵字的地方,就是 next 方法的停
下來的地方,它后面的表達(dá)式的值(即 await file.readLine() 的值),會(huì)作為 next() 返回對(duì)象的 value 屬性,這一點(diǎn)是與同步 Generator 函數(shù)一致
的。
異步 Generator 函數(shù)內(nèi)部,能夠同時(shí)使用 await 和 yield 命令。可以這樣理解, await 命令用于將外部操作產(chǎn)生的值輸入函數(shù)內(nèi)部, yield 命令用于將函
數(shù)內(nèi)部的值輸出。
上面代碼定義的異步 Generator 函數(shù)的用法如下。

(async function () { for await (const line of readLines(filePath)) { console.log(line); } })()

異步 Generator 函數(shù)可以與 for await...of 循環(huán)結(jié)合起來使用。

async function* prefixLines(asyncIterable) { for await (const line of asyncIterable) { yield '> ' + line; } }

異步 Generator 函數(shù)的返回值是一個(gè)異步 Iterator,即每次調(diào)用它的 next 方法,會(huì)返回一個(gè) Promise 對(duì)象,也就是說,跟在 yield 命令后面的,應(yīng)該
是一個(gè) Promise 對(duì)象。

async function* asyncGenerator() { console.log('Start'); const result = await doSomethingAsync(); // (A) yield 'Result: '+ result; // (B) console.log('Done'); } const ag = asyncGenerator(); ag.next().then({value, done} => { // ... })

上面代碼中, ag 是 asyncGenerator 函數(shù)返回的異步 Iterator 對(duì)象。調(diào)用 ag.next() 以后, asyncGenerator 函數(shù)內(nèi)部的執(zhí)行順序如下。
1. 打印出 Start 。
2. await 命令返回一個(gè) Promise 對(duì)象,但是程序不會(huì)停在這里,繼續(xù)往下執(zhí)行。
3. 程序在 B 處暫停執(zhí)行, yield 命令立刻返回一個(gè) Promise 對(duì)象,該對(duì)象就是 ag.next() 的返回值。
4. A 處 await 命令后面的那個(gè) Promise 對(duì)象 resolved,產(chǎn)生的值放入 result 變量。
5. B 處的 Promise 對(duì)象 resolved, then 方法指定的回調(diào)函數(shù)開始執(zhí)行,該函數(shù)的參數(shù)是一個(gè)對(duì)象, value 的值是表達(dá)式 'Result: ' + result 的
值, done 屬性的值是 false 。
A 和 B 兩行的作用類似于下面的代碼。

return new Promise((resolve, reject) => { doSomethingAsync() .then(result => { resolve({ value: 'Result: '+result, done: false, }); }); });

如果異步 Generator 函數(shù)拋出錯(cuò)誤,會(huì)被 Promise 對(duì)象 reject ,然后拋出的錯(cuò)誤被 catch 方法捕獲。

async function* asyncGenerator() { throw new Error('Problem!'); } asyncGenerator() .next() .catch(err => console.log(err)); // Error: Problem!

注意,普通的 async 函數(shù)返回的是一個(gè) Promise 對(duì)象,而異步 Generator 函數(shù)返回的是一個(gè)異步 Iterator 對(duì)象。可以這樣理解,async 函數(shù)和異步
Generator 函數(shù),是封裝異步操作的兩種方法,都用來達(dá)到同一種目的。區(qū)別在于,前者自帶執(zhí)行器,后者通過 for await...of 執(zhí)行,或者自己編寫執(zhí)
行器。下面就是一個(gè)異步 Generator 函數(shù)的執(zhí)行器。

async function takeAsync(asyncIterable, count = Infinity) { const result = []; const iterator = asyncIterable[Symbol.asyncIterator](); while (result.length < count) { const {value, done} = await iterator.next(); if (done) break; result.push(value); } return result; }

上面代碼中,異步 Generator 函數(shù)產(chǎn)生的異步遍歷器,會(huì)通過 while 循環(huán)自動(dòng)執(zhí)行,每當(dāng) await iterator.next() 完成,就會(huì)進(jìn)入下一輪循環(huán)。一旦
done 屬性變?yōu)?true ,就會(huì)跳出循環(huán),異步遍歷器執(zhí)行結(jié)束。
下面是這個(gè)自動(dòng)執(zhí)行器的一個(gè)使用實(shí)例。

async function f() { async function* gen() { yield 'a'; yield 'b'; yield 'c'; } return await takeAsync(gen()); } f().then(function (result) { console.log(result); // ['a', 'b', 'c'] })

異步 Generator 函數(shù)出現(xiàn)以后,JavaScript 就有了四種函數(shù)形式:普通函數(shù)、async 函數(shù)、Generator 函數(shù)和異步 Generator 函數(shù)。請(qǐng)注意區(qū)分每種
函數(shù)的不同之處。基本上,如果是一系列按照順序執(zhí)行的異步操作(比如讀取文件,然后寫入新內(nèi)容,再存入硬盤),可以使用 async 函數(shù);如果是一系
列產(chǎn)生相同數(shù)據(jù)結(jié)構(gòu)的異步操作(比如一行一行讀取文件),可以使用異步 Generator 函數(shù)。
異步 Generator 函數(shù)也可以通過 next 方法的參數(shù),接收外部傳入的數(shù)據(jù)

const writer = openFile('someFile.txt'); writer.next('hello'); // 立即執(zhí)行 writer.next('world'); // 立即執(zhí)行 await writer.return(); // 等待寫入結(jié)束

上面代碼中, openFile 是一個(gè)異步 Generator 函數(shù)。 next 方法的參數(shù),向該函數(shù)內(nèi)部的操作傳入數(shù)據(jù)。每次 next 方法都是同步執(zhí)行的,最后的 await
命令用于等待整個(gè)寫入操作結(jié)束。
最后,同步的數(shù)據(jù)結(jié)構(gòu),也可以使用異步 Generator 函數(shù)。

async function* createAsyncIterable(syncIterable) { for (const elem of syncIterable) { yield elem; } }

上面代碼中,由于沒有異步操作,所以也就沒有使用 await 關(guān)鍵字。

7.4yield* 語句

yield* 語句也可以跟一個(gè)異步遍歷器。

async function* gen1() { yield 'a'; yield 'b'; return 2; } async function* gen2() { // result 最終會(huì)等于 2 const result = yield* gen1(); }

上面代碼中, gen2 函數(shù)里面的 result 變量,最后的值是 2 。
與同步 Generator 函數(shù)一樣, for await...of 循環(huán)會(huì)展開 yield* 。

(async function () { for await (const x of gen2()) { console.log(x); } })(); // a // b

總結(jié)

本博客源于本人閱讀相關(guān)書籍和視頻總結(jié),創(chuàng)作不易,謝謝點(diǎn)贊支持。學(xué)到就是賺到。我是歌謠,勵(lì)志成為一名優(yōu)秀的技術(shù)革新人員。

歡迎私信交流,一起學(xué)習(xí),一起成長(zhǎng)。

推薦鏈接 其他文件目錄參照

“睡服“面試官系列之各系列目錄匯總(建議學(xué)習(xí)收藏)

總結(jié)

以上是生活随笔為你收集整理的“睡服”面试官系列第十九篇之async函数(建议收藏学习)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。