欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

如何更好的編寫js async函數(shù)

 更新時間:2018年05月13日 10:34:00   作者:賈順名  
本文給大家談一下如何優(yōu)化async代碼,更充分的利用異步事件流杜絕濫用async,感興趣的朋友跟隨腳本之家小編一起學習吧

2018年已經(jīng)到了5月份,node的4.x版本也已經(jīng)停止了維護我司的某個服務也已經(jīng)切到了8.x,目前正在做koa2.x的遷移將之前的generator全部替換為async但是,在替換的過程中,發(fā)現(xiàn)一些濫用async導致的時間上的浪費 所以來談一下,如何優(yōu)化async代碼,更充分的利用異步事件流 杜絕濫用async

首先,你需要了解Promise

Promise是使用async/await的基礎,所以你一定要先了解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的實例,在實例化的過程中進行文件的讀取,當文件讀取的回調(diào)觸發(fā)式,進行Promise狀態(tài)的變更,resolved或者rejected狀態(tài)的變更我們使用then來監(jiān)聽,第一個回調(diào)為resolve的處理,第二個回調(diào)為reject的處理。

async與Promise的關系

async函數(shù)相當于一個簡寫的返回Promise實例的函數(shù),效果如下:

function getNumber () {
 return new Promise((resolve, reject) => {
  resolve(1)
 })
}
// =>
async function getNumber () {
 return 1
}

兩者在使用上方式上完全一樣,都可以在調(diào)用getNumber函數(shù)后使用then進行監(jiān)聽返回值。 以及與async對應的await語法的使用方式:

getNumber().then(data => {
 // got data
})
// =>
let data = await getNumber()

await的執(zhí)行會獲取表達式后邊的Promise執(zhí)行結果,相當于我們調(diào)用then獲取回調(diào)結果一樣。 P.S. 在async/await支持度還不是很高的時候,大家都會選擇使用generator/yield結合著一些類似于co的庫來實現(xiàn)類似的效果

async函數(shù)代碼執(zhí)行是同步的,結果返回是異步的

async函數(shù)總是會返回一個Promise的實例 這點兒很重要所以說調(diào)用一個async函數(shù)時,可以理解為里邊的代碼都是處于new Promise中,所以是同步執(zhí)行的而最后return的操作,則相當于在Promise中調(diào)用resolve:

async function getNumber () {
 console.log('call getNumber()')

 return 1
}
getNumber().then(_ => console.log('resolved'))
console.log('done')
// 輸出順序:
// call getNumber()
// done
// resolved

Promise內(nèi)部的Promise會被消化

也就是說,如果我們有如下的代碼:

function getNumber () {
 return new Promise(resolve => {
  resolve(Promise.resolve(1))
 })
}
getNumber().then(data => console.log(data)) // 1

如果按照上邊說的話,我們在then里邊獲取到的data應該是傳入resolve中的值 ,也就是另一個Promise的實例。
 但實際上,我們會直接獲得返回值:1,也就是說,如果在Promise中返回一個Promise,實際上程序會幫我們執(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關鍵字

如果忘記添加await關鍵字,代碼層面并不會報錯,但是我們接收到的返回值卻是一個Promise

let number = getNumber()
console.log(number) // Promise

所以在使用時一定要切記await關鍵字

let number = await getNumber()
console.log(number) // 1

不是所有的地方都需要添加await

在代碼的執(zhí)行過程中,有時候,并不是所有的異步都要添加await的。 比如下邊的對文件的操作:

我們假設fs所有的API都被我們轉(zhuǎn)換為了Promise版本

let number = await getNumber()
console.log(number) // 1

我們通過await打開一個文件,然后進行兩次文件的寫入。

但是注意了,在兩次文件的寫入操作前邊,我們并沒有添加await關鍵字。
 因為這是多余的,我們只需要通知API,我要往這個文件里邊寫入一行文本,順序自然會由fs來控制
 然后我們在最后使用await來關閉這個文件。
 因為如果我們上邊在執(zhí)行寫入的過程還沒有完成時,close的回調(diào)是不會觸發(fā)的,
 也就是說,回調(diào)的觸發(fā)就意味著上邊兩步的write已經(jīng)執(zhí)行完成了。

合并多個不相干的async函數(shù)調(diào)用

如果我們現(xiàn)在要獲取一個用戶的頭像和用戶的詳細信息(而這是兩個接口 雖說一般情況下不太會出現(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來確保兩者都返回結果。

讓相互沒有依賴關系的異步函數(shù)同時執(zhí)行

一些循環(huán)中的注意事項

forEach

當我們調(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的輸出,但是當我們在await getUsersInfo()下邊再添加一條console.log('done')的話,就會發(fā)現(xiàn):

 我們會先得到done,然后才是三條uid的log,也就是說,getUsersInfo返回結果時,其實內(nèi)部Promise并沒有執(zhí)行完。
 這是因為forEach并不會關心回調(diào)函數(shù)的返回值是什么,它只是運行回調(diào)。

不要在普通的for、while循環(huán)中使用await

使用普通的for、while循環(huán)會導致程序變?yōu)榇校?/p>

for (let uid of [1, 2, 3]) {
 let result = await getUserInfo(uid)
}

這樣的代碼運行,會在拿到uid: 1的數(shù)據(jù)后才會去請求uid: 2的數(shù)據(jù)

--------------------------------------------------------------------------------

關于這兩種問題的解決方案:

目前最優(yōu)的就是將其替換為map結合著Promise.all來實現(xiàn):

await Promise.all([1, 2, 3].map(async uid => await getUserInfo(uid)))

這樣的代碼實現(xiàn)會同時實例化三個Promise,并請求getUserInfo

P.S. 草案中有一個await*,可以省去Promise.all

await Promise.all([1, 2, 3].map(async uid => await getUserInfo(uid)))

P.S. 為什么在使用Generator+co時沒有這個問題

在使用koa1.x的時候,我們直接寫yield [].map是不會出現(xiàn)上述所說的串行問題的看過co源碼的小伙伴應該都明白,里邊有這么兩個函數(shù)(刪除了其余不相關的代碼):

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大佬)。

總結

總結一下關于async函數(shù)編寫的幾個小提示:

1.使用return Promise.reject()在async函數(shù)中拋出異常
2.讓相互之間沒有依賴關系的異步函數(shù)同時執(zhí)行
3.不要在循環(huán)的回調(diào)中/for、while循環(huán)中使用await,用map來代替它

參考資料

1.async-function-tips

總結

以上所述是小編給大家介紹的如何更好的編寫js async函數(shù),希望對大家有所幫助如果大家有任何疑問歡迎給我留言,小編會及時回復大家的!

相關文章

最新評論