詳解Node.js中的Async和Await函數(shù)
在本文中,你將學(xué)習(xí)如何使用Node.js中的async函數(shù)(async/await)來簡化callback或Promise.
異步語言結(jié)構(gòu)在其他語言中已經(jīng)存在了,像c#的async/await、Kotlin的coroutines、go的goroutines,隨著Node.js 8的發(fā)布,期待已久的async函數(shù)也在其中默認(rèn)實現(xiàn)了。
Node中的async函數(shù)是什么?
當(dāng)函數(shù)聲明為一個Async函數(shù)它會返回一個 AsyncFunction 對象,它們類似于 Generator 因為執(zhí)可以被暫停。唯一的區(qū)別是它們返回的是 Promise 而不是 { value: any, done: Boolean } 對象。不過它們還是非常相似,你可以使用 co 包來獲取同樣的功能。
在async函數(shù)中,可以等待 Promise 完成或捕獲它拒絕的原因。
如果你要在Promise中實現(xiàn)一些自己的邏輯的話
function handler (req, res) {
return request('https://user-handler-service')
.catch((err) => {
logger.error('Http error', err)
error.logged = true
throw err
})
.then((response) => Mongo.findOne({ user: response.body.user }))
.catch((err) => {
!error.logged && logger.error('Mongo error', err)
error.logged = true
throw err
})
.then((document) => executeLogic(req, res, document))
.catch((err) => {
!error.logged && console.error(err)
res.status(500).send()
})
}
可以使用 async/await 讓這個代碼看起來像同步執(zhí)行的代碼
async function handler (req, res) {
let response
try {
response = await request('https://user-handler-service')
} catch (err) {
logger.error('Http error', err)
return res.status(500).send()
}
let document
try {
document = await Mongo.findOne({ user: response.body.user })
} catch (err) {
logger.error('Mongo error', err)
return res.status(500).send()
}
executeLogic(document, req, res)
}
在老的v8版本中,如果有有個 promise 的拒絕沒有被處理你會得到一個警告,可以不用創(chuàng)建一個拒絕錯誤監(jiān)聽函數(shù)。然而,建議在這種情況下退出你的應(yīng)用程序。因為當(dāng)你不處理錯誤時,應(yīng)用程序處于一個未知的狀態(tài)。
process.on('unhandledRejection', (err) => {
console.error(err)
process.exit(1)
})
async函數(shù)模式
在處理異步操作時,有很多例子讓他們就像處理同步代碼一樣。如果使用 Promise 或 callbacks 來解決問題時需要使用很復(fù)雜的模式或者外部庫。
當(dāng)需要再循環(huán)中使用異步獲取數(shù)據(jù)或使用 if-else 條件時就是一種很復(fù)雜的情況。
指數(shù)回退機(jī)制
使用 Promise 實現(xiàn)回退邏輯相當(dāng)笨拙
function requestWithRetry (url, retryCount) {
if (retryCount) {
return new Promise((resolve, reject) => {
const timeout = Math.pow(2, retryCount)
setTimeout(() => {
console.log('Waiting', timeout, 'ms')
_requestWithRetry(url, retryCount)
.then(resolve)
.catch(reject)
}, timeout)
})
} else {
return _requestWithRetry(url, 0)
}
}
function _requestWithRetry (url, retryCount) {
return request(url, retryCount)
.catch((err) => {
if (err.statusCode && err.statusCode >= 500) {
console.log('Retrying', err.message, retryCount)
return requestWithRetry(url, ++retryCount)
}
throw err
})
}
requestWithRetry('http://localhost:3000')
.then((res) => {
console.log(res)
})
.catch(err => {
console.error(err)
})
代碼看的讓人很頭疼,你也不會想看這樣的代碼。我們可以使用async/await重新這個例子,使其更簡單
function wait (timeout) {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, timeout)
})
}
async function requestWithRetry (url) {
const MAX_RETRIES = 10
for (let i = 0; i <= MAX_RETRIES; i++) {
try {
return await request(url)
} catch (err) {
const timeout = Math.pow(2, i)
console.log('Waiting', timeout, 'ms')
await wait(timeout)
console.log('Retrying', err.message, i)
}
}
}
上面代碼看起來很舒服對不對
中間值
不像前面的例子那么嚇人,如果你有3個異步函數(shù)依次相互依賴的情況,那么你必須從幾個難看的解決方案中進(jìn)行選擇。
functionA 返回一個 Promise ,那么 functionB 需要這個值而 functioinC 需要 functionA 和 functionB 完成后的值。
方案1: then 圣誕樹
function executeAsyncTask () {
return functionA()
.then((valueA) => {
return functionB(valueA)
.then((valueB) => {
return functionC(valueA, valueB)
})
})
}
用這個解決方案,我們在第三個 then 中可以獲得 valueA 和 valueB ,然后可以向前面兩個 then 一樣獲得 valueA 和 valueB 的值。這里不能將圣誕樹(毀掉地獄)拉平,如果這樣做的話會丟失閉包, valueA 在 functioinC 中將不可用。
方案2:移動到上一級作用域
function executeAsyncTask () {
let valueA
return functionA()
.then((v) => {
valueA = v
return functionB(valueA)
})
.then((valueB) => {
return functionC(valueA, valueB)
})
}
在這顆圣誕樹中,我們使用更高的作用域保變量 valueA ,因為 valueA 作用域在所有的 then 作用域外面,所以 functionC 可以拿到第一個 functionA 完成的值。
這是一個很有效扁平化 .then 鏈"正確"的語法,然而,這種方法我們需要使用兩個變量 valueA 和 v 來保存相同的值。
方案3:使用一個多余的數(shù)組
function executeAsyncTask () {
return functionA()
.then(valueA => {
return Promise.all([valueA, functionB(valueA)])
})
.then(([valueA, valueB]) => {
return functionC(valueA, valueB)
})
}
在函數(shù) functionA 的 then 中使用一個數(shù)組將 valueA 和 Promise 一起返回,這樣能有效的扁平化圣誕樹(回調(diào)地獄)。
方案4:寫一個幫助函數(shù)
const converge = (...promises) => (...args) => {
let [head, ...tail] = promises
if (tail.length) {
return head(...args)
.then((value) => converge(...tail)(...args.concat([value])))
} else {
return head(...args)
}
}
functionA(2)
.then((valueA) => converge(functionB, functionC)(valueA))
這樣是可行的,寫一個幫助函數(shù)來屏蔽上下文變量聲明。但是這樣的代碼非常不利于閱讀,對于不熟悉這些魔法的人就更難了。
使用 async/await 我們的問題神奇般的消失
async function executeAsyncTask () {
const valueA = await functionA()
const valueB = await functionB(valueA)
return function3(valueA, valueB)
}
使用 async/await 處理多個平行請求
和上面一個差不多,如果你想一次執(zhí)行多個異步任務(wù),然后在不同的地方使用它們的值可以使用 async/await 輕松搞定。
async function executeParallelAsyncTasks () {
const [ valueA, valueB, valueC ] = await Promise.all([ functionA(), functionB(), functionC() ])
doSomethingWith(valueA)
doSomethingElseWith(valueB)
doAnotherThingWith(valueC)
}
數(shù)組迭代方法
你可以在 map 、 filter 、 reduce 方法中使用async函數(shù),雖然它們看起來不是很直觀,但是你可以在控制臺中實驗以下代碼。
1.map
function asyncThing (value) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(value), 100)
})
}
async function main () {
return [1,2,3,4].map(async (value) => {
const v = await asyncThing(value)
return v * 2
})
}
main()
.then(v => console.log(v))
.catch(err => console.error(err))
2.filter
function asyncThing (value) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(value), 100)
})
}
async function main () {
return [1,2,3,4].filter(async (value) => {
const v = await asyncThing(value)
return v % 2 === 0
})
}
main()
.then(v => console.log(v))
.catch(err => console.error(err))
3.reduce
function asyncThing (value) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(value), 100)
})
}
async function main () {
return [1,2,3,4].reduce(async (acc, value) => {
return await acc + await asyncThing(value)
}, Promise.resolve(0))
}
main()
.then(v => console.log(v))
.catch(err => console.error(err))
解決方案:
[ Promise { <pending> }, Promise { <pending> }, Promise { <pending> }, Promise { <pending> } ]
[ 1, 2, 3, 4 ]
10
如果是map迭代數(shù)據(jù)你會看到返回值為 [ 2, 4, 6, 8 ] ,唯一的問題是每個值被 AsyncFunction 函數(shù)包裹在了一個 Promise 中
所以如果想要獲得它們的值,需要將數(shù)組傳遞給 Promise.All() 來解開 Promise 的包裹。
main()
.then(v => Promise.all(v))
.then(v => console.log(v))
.catch(err => console.error(err))
一開始你會等待 Promise 解決,然后使用map遍歷每個值
function main () {
return Promise.all([1,2,3,4].map((value) => asyncThing(value)))
}
main()
.then(values => values.map((value) => value * 2))
.then(v => console.log(v))
.catch(err => console.error(err))
這樣好像更簡單一些?
如果在你的迭代器中如果你有一個長時間運(yùn)行的同步邏輯和另一個長時間運(yùn)行的異步任務(wù),async/await版本任然常有用
這種方式當(dāng)你能拿到第一個值,就可以開始做一些計算,而不必等到所有 Promise 完成才運(yùn)行你的計算。盡管結(jié)果包裹在 Promise 中,但是如果按順序執(zhí)行結(jié)果會更快。
關(guān)于 filter 的問題
你可能發(fā)覺了,即使上面filter函數(shù)里面返回了 [ false, true, false, true ] , await asyncThing(value) 會返回一個 promise 那么你肯定會得到一個原始的值。你可以在return之前等待所有異步完成,在進(jìn)行過濾。
Reducing很簡單,有一點需要注意的就是需要將初始值包裹在 Promise.resolve 中
重寫基于callback的node應(yīng)用成
Async 函數(shù)默認(rèn)返回一個 Promise ,所以你可以使用 Promises 來重寫任何基于 callback 的函數(shù),然后 await 等待他們執(zhí)行完畢。在node中也可以使用 util.promisify 函數(shù)將基于回調(diào)的函數(shù)轉(zhuǎn)換為基于 Promise 的函數(shù)
重寫基于Promise的應(yīng)用程序
要轉(zhuǎn)換很簡單, .then 將Promise執(zhí)行流串了起來?,F(xiàn)在你可以直接使用`async/await。
function asyncTask () {
return functionA()
.then((valueA) => functionB(valueA))
.then((valueB) => functionC(valueB))
.then((valueC) => functionD(valueC))
.catch((err) => logger.error(err))
}
轉(zhuǎn)換后
async function asyncTask () {
try {
const valueA = await functionA()
const valueB = await functionB(valueA)
const valueC = await functionC(valueB)
return await functionD(valueC)
} catch (err) {
logger.error(err)
}
}
Rewriting Nod
使用 Async/Await 將很大程度上的使應(yīng)用程序具有高可讀性,降低應(yīng)用程序的處理復(fù)雜度(如:錯誤捕獲),如果你也使用 node v8+的版本不妨嘗試一下,或許會有新的收獲。
總結(jié)
以上所述是小編給大家介紹的Node.js中的Async和Await函數(shù),希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
node.js實現(xiàn)端口轉(zhuǎn)發(fā)
這篇文章主要為大家詳細(xì)介紹了node.js實現(xiàn)端口轉(zhuǎn)發(fā)的關(guān)鍵代碼,感興趣的小伙伴們可以參考一下2016-04-04
干凈卸載Windows的Node.js環(huán)境的方法
這篇文章主要介紹了如何干凈卸載Windows的Node.js環(huán)境的方法,文中通過圖文結(jié)合的方式講解的非常詳細(xì),對大家刪除Node.js環(huán)境有一定的幫助,需要的朋友可以參考下2025-01-01
node.js中的events.emitter.once方法使用說明
這篇文章主要介紹了node.js中的events.emitter.once方法使用說明,本文介紹了events.emitter.once的方法說明、語法、接收參數(shù)、使用實例和實現(xiàn)源碼,需要的朋友可以參考下2014-12-12
node.js實現(xiàn)微信JS-API封裝接口的示例代碼
這篇文章主要介紹了node.js實現(xiàn)微信JS-API封裝接口的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09
Node.js node-schedule定時任務(wù)隔多少分鐘執(zhí)行一次的方法
這篇文章主要介紹了Node.js node-schedule定時任務(wù)隔多少分鐘執(zhí)行一次的方法,本文給出了每隔 15 分鐘、 30 分鐘執(zhí)行一次任務(wù)的編碼實例,需要的朋友可以參考下2015-02-02
nodejs如何讀取文件二進(jìn)制 前端響應(yīng)blob或base64顯示圖片
這篇文章主要介紹了nodejs如何讀取文件二進(jìn)制 前端響應(yīng)blob或base64顯示圖片方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08
Node.js 使用 Express-Jwt和JsonWebToken 進(jìn)行Token身份
這篇文章主要介紹了Node.js 使用 Express-Jwt和JsonWebToken 進(jìn)行Token身份驗證的操作方法,本文通過實例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-08-08

