異步語言結(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))
這樣好像更簡單一些?
如果在你的迭代器中如果你有一個長時間運行的同步邏輯和另一個長時間運行的異步任務(wù),async/await版本任然常有用
這種方式當(dāng)你能拿到第一個值,就可以開始做一些計算,而不必等到所有 Promise 完成才運行你的計算。盡管結(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í)行流串了起來。現(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+的版本不妨嘗試一下,或許會有新的收獲。
相關(guān)推薦:
ES6之a(chǎn)sync+await 同步/異步方案
NodeJs通過async和await處理異步的方法
ES7的async/await用法實例詳解
以上就是Node.js中的Async和Await函數(shù)解析的詳細(xì)內(nèi)容,更多請關(guān)注php中文網(wǎng)其它相關(guān)文章!