摘要:一個簡單的轉(zhuǎn)換為的例子我們調(diào)用函數(shù)返回一個的實(shí)例,在實(shí)例化的過程中進(jìn)行文件的讀取,當(dāng)文件讀取的回調(diào)觸發(fā)式,進(jìn)行狀態(tài)的變更,或者狀態(tài)的變更我們使用來監(jiān)聽,第一個回調(diào)為的處理,第二個回調(diào)為的處理。
2018年已經(jīng)到了5月份,node的4.x版本也已經(jīng)停止了維護(hù)首先,你需要了解Promise
我司的某個服務(wù)也已經(jīng)切到了8.x,目前正在做koa2.x的遷移
將之前的generator全部替換為async
但是,在替換的過程中,發(fā)現(xiàn)一些濫用async導(dǎo)致的時間上的浪費(fèi)
所以來談一下,如何優(yōu)化async代碼,更充分的利用異步事件流 杜絕濫用async
Promise是使用async/await的基礎(chǔ),所以你一定要先了解Promise是做什么的
Promise是幫助解決回調(diào)地獄的一個好東西,能夠讓異步流程變得更清晰。
一個簡單的Error-first-callback轉(zhuǎn)換為Promise的例子:
const fs = require("fs") function readFile (fileName) { return new Promise((resolve, reject) => { fs.readFile(fileName, (err, data) => { if (err) reject(err) resolve(data) }) }) } readFile("test.log").then(data => { console.log("get data") }, err => { console.error(err) })
我們調(diào)用函數(shù)返回一個Promise的實(shí)例,在實(shí)例化的過程中進(jìn)行文件的讀取,當(dāng)文件讀取的回調(diào)觸發(fā)式,進(jìn)行Promise狀態(tài)的變更,resolved或者rejected
狀態(tài)的變更我們使用then來監(jiān)聽,第一個回調(diào)為resolve的處理,第二個回調(diào)為reject的處理。
async函數(shù)相當(dāng)于一個簡寫的返回Promise實(shí)例的函數(shù),效果如下:
function getNumber () { return new Promise((resolve, reject) => { resolve(1) }) } // => async function getNumber () { return 1 }
兩者在使用上方式上完全一樣,都可以在調(diào)用getNumber函數(shù)后使用then進(jìn)行監(jiān)聽返回值。
以及與async對應(yīng)的await語法的使用方式:
getNumber().then(data => { // got data }) // => let data = await getNumber()
await的執(zhí)行會獲取表達(dá)式后邊的Promise執(zhí)行結(jié)果,相當(dāng)于我們調(diào)用then獲取回調(diào)結(jié)果一樣。
P.S. 在async/await支持度還不是很高的時候,大家都會選擇使用generator/yield結(jié)合著一些類似于co的庫來實(shí)現(xiàn)類似的效果
async函數(shù)總是會返回一個Promise的實(shí)例 這點(diǎn)兒很重要
所以說調(diào)用一個async函數(shù)時,可以理解為里邊的代碼都是處于new Promise中,所以是同步執(zhí)行的
而最后return的操作,則相當(dāng)于在Promise中調(diào)用resolve:
async function getNumber () { console.log("call getNumber()") return 1 } getNumber().then(_ => console.log("resolved")) console.log("done") // 輸出順序: // call getNumber() // done // resolvedPromise內(nèi)部的Promise會被消化
也就是說,如果我們有如下的代碼:
function getNumber () { return new Promise(resolve => { resolve(Promise.resolve(1)) }) } getNumber().then(data => console.log(data)) // 1
如果按照上邊說的話,我們在then里邊獲取到的data應(yīng)該是傳入resolve中的值 ,也就是另一個Promise的實(shí)例。
但實(shí)際上,我們會直接獲得返回值:1,也就是說,如果在Promise中返回一個Promise,實(shí)際上程序會幫我們執(zhí)行這個Promise,并在內(nèi)部的Promise狀態(tài)改變時觸發(fā)then之類的回調(diào)。
一個有意思的事情:
function getNumber () { return new Promise(resolve => { resolve(Promise.reject(new Error("Test"))) }) } getNumber().catch(err => console.error(err)) // Error: Test
如果我們在resolve中傳入了一個reject,則我們在外部則可以直接使用catch監(jiān)聽到。
這種方式經(jīng)常用于在async函數(shù)中拋出異常
如何在async函數(shù)中拋出異常:
async function getNumber () { return Promise.reject(new Error("Test")) } try { let number = await getNumber() } catch (e) { console.error(e) }一定不要忘了await關(guān)鍵字
如果忘記添加await關(guān)鍵字,代碼層面并不會報錯,但是我們接收到的返回值卻是一個Promise
let number = getNumber() console.log(number) // Promise
所以在使用時一定要切記await關(guān)鍵字
let number = await getNumber() console.log(number) // 1不是所有的地方都需要添加await
在代碼的執(zhí)行過程中,有時候,并不是所有的異步都要添加await的。
比如下邊的對文件的操作:
我們假設(shè)fs所有的API都被我們轉(zhuǎn)換為了Promise版本
async function writeFile () { let fd = await fs.open("test.log") fs.write(fd, "hello") fs.write(fd, "world") return fs.close(fd) }
就像上邊說的,Promise內(nèi)部的Promise會被消化,所以我們在最后的close也沒有使用await
我們通過await打開一個文件,然后進(jìn)行兩次文件的寫入。
但是注意了,在兩次文件的寫入操作前邊,我們并沒有添加await關(guān)鍵字。
因?yàn)檫@是多余的,我們只需要通知API,我要往這個文件里邊寫入一行文本,順序自然會由fs來控制 。
最后再進(jìn)行close,因?yàn)槿绻覀兩线呍趫?zhí)行寫入的過程還沒有完成時,close的回調(diào)是不會觸發(fā)的,
也就是說,回調(diào)的觸發(fā)就意味著上邊兩步的write已經(jīng)執(zhí)行完成了。
如果我們現(xiàn)在要獲取一個用戶的頭像和用戶的詳細(xì)信息(而這是兩個接口 雖說一般情況下不太會出現(xiàn))
async function getUser () { let avatar = await getAvatar() let userInfo = await getUserInfo() return { avatar, userInfo } }
這樣的代碼就造成了一個問題,我們獲取用戶信息的接口并不依賴于頭像接口的返回值。
但是這樣的代碼卻會在獲取到頭像以后才會去發(fā)送獲取用戶信息的請求。
所以我們對這種代碼可以這樣處理:
async function getUser () { let [avatar, userInfo] = await Promise.all([getAvatar(), getUserInfo()]) return { avatar, userInfo } }
這樣的修改就會讓getAvatar與getUserInfo內(nèi)部的代碼同時執(zhí)行,同時發(fā)送兩個請求,在外層通過包一層Promise.all來確保兩者都返回結(jié)果。
讓相互沒有依賴關(guān)系的異步函數(shù)同時執(zhí)行
一些循環(huán)中的注意事項(xiàng) forEach當(dāng)我們調(diào)用這樣的代碼時:
async function getUsersInfo () { [1, 2, 3].forEach(async uid => { console.log(await getUserInfo(uid)) }) } function getuserInfo (uid) { return new Promise(resolve => { setTimeout(_ => resolve(uid), 1000) }) } await getUsersInfo()
這樣的執(zhí)行好像并沒有什么問題,我們也會得到1、2、3三條log的輸出,
但是當(dāng)我們在await getUsersInfo()下邊再添加一條console.log("done")的話,就會發(fā)現(xiàn):
我們會先得到done,然后才是三條uid的log,也就是說,getUsersInfo返回結(jié)果時,其實(shí)內(nèi)部Promise并沒有執(zhí)行完。
這是因?yàn)?b>forEach并不會關(guān)心回調(diào)函數(shù)的返回值是什么,它只是運(yùn)行回調(diào)。
使用普通的for、while循環(huán)會導(dǎo)致程序變?yōu)榇校?/p>
for (let uid of [1, 2, 3]) { let result = await getUserInfo(uid) }
這樣的代碼運(yùn)行,會在拿到uid: 1的數(shù)據(jù)后才會去請求uid: 2的數(shù)據(jù)
關(guān)于這兩種問題的解決方案:目前最優(yōu)的就是將其替換為map結(jié)合著Promise.all來實(shí)現(xiàn):
await Promise.all([1, 2, 3].map(async uid => await getUserInfo(uid)))
這樣的代碼實(shí)現(xiàn)會同時實(shí)例化三個Promise,并請求getUserInfo
P.S. 草案中有一個await*,可以省去Promise.allawait* [1, 2, 3].map(async uid => await getUserInfo(uid))P.S. 為什么在使用Generator+co時沒有這個問題
在使用koa1.x的時候,我們直接寫yield [].map是不會出現(xiàn)上述所說的串行問題的
看過co源碼的小伙伴應(yīng)該都明白,里邊有這么兩個函數(shù)(刪除了其余不相關(guān)的代碼):
function toPromise(obj) { if (Array.isArray(obj)) return arrayToPromise.call(this, obj); return obj; } function arrayToPromise(obj) { return Promise.all(obj.map(toPromise, this)); }
co是幫助我們添加了Promise.all的處理的(膜拜TJ大佬)。
總結(jié)總結(jié)一下關(guān)于async函數(shù)編寫的幾個小提示:
使用return Promise.reject()在async函數(shù)中拋出異常
讓相互之間沒有依賴關(guān)系的異步函數(shù)同時執(zhí)行
不要在循環(huán)的回調(diào)中/for、while循環(huán)中使用await,用map來代替它
參考資料async-function-tips
本人GitHub: jiasm 歡迎小伙伴們follow、交流
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/94941.html
摘要:大家都一直在嘗試使用更好的方案來解決這些問題。這是一個用同步的思維來解決異步問題的方案。當(dāng)我們發(fā)出了請求,并不會等待響應(yīng)結(jié)果,而是會繼續(xù)執(zhí)行后面的代碼,響應(yīng)結(jié)果的處理在之后的事件循環(huán)中解決。我們可以用一個兩人問答的場景來比喻異步與同步。 在實(shí)際開發(fā)中總會遇到許多異步的問題,最常見的場景便是接口請求之后一定要等一段時間才能得到結(jié)果,如果遇到多個接口前后依賴,那么問題就變得復(fù)雜。大家都一直...
摘要:如果你還沒讀過上篇上篇和中篇并無依賴關(guān)系,您可以讀過本文之后再閱讀上篇,可戳面試篇寒冬求職季之你必須要懂的原生上小姐姐花了近百個小時才完成這篇文章,篇幅較長,希望大家閱讀時多花點(diǎn)耐心,力求真正的掌握相關(guān)知識點(diǎn)。 互聯(lián)網(wǎng)寒冬之際,各大公司都縮減了HC,甚至是采取了裁員措施,在這樣的大環(huán)境之下,想要獲得一份更好的工作,必然需要付出更多的努力。 一年前,也許你搞清楚閉包,this,原型鏈,就能獲得...
摘要:事件循環(huán)從回調(diào)隊(duì)列中獲取并將其推入調(diào)用堆棧。執(zhí)行從調(diào)用堆棧中移除從調(diào)用堆棧中移除快速回顧值得注意的是,指定了事件循環(huán)應(yīng)該如何工作,這意味著在技術(shù)上它屬于引擎的職責(zé)范圍,不再僅僅扮演宿主環(huán)境的角色。 此篇是 JavaScript是如何工作的第四篇,其它三篇可以看這里: JavaScript是如何工作的:引擎,運(yùn)行時和調(diào)用堆棧的概述! JavaScript是如何工作的:深入V8引擎&編寫...
摘要:感謝大神的免費(fèi)的計算機(jī)編程類中文書籍收錄并推薦地址,以后在倉庫里更新地址,聲音版全文狼叔如何正確的學(xué)習(xí)簡介現(xiàn)在,越來越多的科技公司和開發(fā)者開始使用開發(fā)各種應(yīng)用。 說明 2017-12-14 我發(fā)了一篇文章《沒用過Node.js,就別瞎逼逼》是因?yàn)橛腥嗽谥跎虾贜ode.js。那篇文章的反響還是相當(dāng)不錯的,甚至連著名的hax賀老都很認(rèn)同,下班時讀那篇文章,竟然坐車的還坐過站了。大家可以很...
摘要:感謝大神的免費(fèi)的計算機(jī)編程類中文書籍收錄并推薦地址,以后在倉庫里更新地址,聲音版全文狼叔如何正確的學(xué)習(xí)簡介現(xiàn)在,越來越多的科技公司和開發(fā)者開始使用開發(fā)各種應(yīng)用。 說明 2017-12-14 我發(fā)了一篇文章《沒用過Node.js,就別瞎逼逼》是因?yàn)橛腥嗽谥跎虾贜ode.js。那篇文章的反響還是相當(dāng)不錯的,甚至連著名的hax賀老都很認(rèn)同,下班時讀那篇文章,竟然坐車的還坐過站了。大家可以很...
閱讀 1166·2021-11-22 15:22
閱讀 3837·2021-10-19 13:13
閱讀 3570·2021-10-08 10:05
閱讀 3292·2021-09-26 10:20
閱讀 2984·2019-08-29 14:21
閱讀 2192·2019-08-27 10:55
閱讀 1871·2019-08-26 10:31
閱讀 2578·2019-08-23 16:47