原著:6 Reasons Why JavaScript's Async/Await Blows Promises Away (Tutorial)
作者:Mostafa Gaafar
譯者:Tingrui Li
先說一件事,Node自從7.6版本之后已經(jīng)原生支持async/await語法域蜗。如果你還未嘗試過它巨双,這里有一些例子告訴你為何你應(yīng)當馬上開始使用它,并不再回頭霉祸。
Async/await 101
對于那些沒有聽說過這個玩意兒的人筑累,這里有一些快速掃盲:
- Async/await是編寫異步代碼新的方式,之前我們使用回調(diào)函數(shù)和Promise丝蹭。
- Async/await實際上是基于Promise的慢宗。它不能與常規(guī)回調(diào)函數(shù)和node回調(diào)函數(shù)一起使用。
- Async/await跟Promise一樣奔穿,不會阻塞程序婆廊。
- Async/await讓異步代碼看起來更像同步執(zhí)行的。這就是它強大的地方所在巫橄。
語法
假設(shè)一個方法 getJSON
返回一個Promise淘邻,這個Promise解析(resolve)出一個JSON對象,我們想要調(diào)用他并且打印那個JSON湘换,返回"done"
宾舅。
你用Promise通常會這樣實現(xiàn):
const makeRequest = () =>
getJSON()
.then(data => {
console.log(data)
return "done"
})
makeRequest()
而如果你用async/await:
const makeRequest = async () => {
console.log(await getJSON())
return "done"
}
makeRequest()
這里有一些區(qū)別:
我們的方法前面有一個關(guān)鍵字
async
。而await
關(guān)鍵字只能被用于以async
定義的方法內(nèi)部彩倚。任何async
方法都隱式地返回一個Promise筹我,并且解析(resolve)的值是你從方法內(nèi)return
出來的東西(在這個例子中就是string"done"
)。上面第一點表示我們不能在外層是用
await
帆离,因為我們只能在async
方法中使用它蔬蕊。
// 在外層不好使
// await makeRequest()
// 好使
makeRequest().then((result) => {
// do something
})
-
await getJSON()
表示console.log會等到getJSON()
這個Promise resolve之后才會打印它的值。
為什么這樣更好哥谷?
1. 簡潔岸夯、干凈
瞧瞧我們少寫了多少東西!就算是上面這么一個簡短的例子们妥,我們都明顯少寫了很多代碼猜扮。不需要寫.then
,創(chuàng)建異步函數(shù)去處理response监婶,或者對我們根本用不到的變量起data
這個名字旅赢。我們也避免了把代碼寫成很多層齿桃。在下面的例子中,將會凸顯這一點煮盼。
2. 錯誤處理
Async/await讓我們終于可以以同一種方法同時處理同步和異步錯誤:經(jīng)典的try/catch
短纵。在下面這個Promise的例子中,try/catch
不會處理JSON.parse
錯誤僵控,因為那是在Promise中發(fā)生的香到。我們得在Promise中去.catch
,然后重新寫一次我們的錯誤處理代碼喉祭,這可能比你的生產(chǎn)環(huán)境代碼中的那些console.log
要冗余的多。
const makeRequest = () => {
try {
getJSON()
.then(result => {
// JSON.parse可能會失敗
const data = JSON.parse(result)
console.log(data)
})
// 處理JSON.parse可能拋出的錯誤
.catch((err) => {
console.log(err)
})
} catch (err) {
console.log(err)
}
}
現(xiàn)在我們用async/await寫這段代碼雷绢。catch
代碼塊現(xiàn)在會處理JSON轉(zhuǎn)換錯誤了泛烙。
const makeRequest = async () => {
try {
// JSON.parse可能會失敗
const data = JSON.parse(await getJSON())
console.log(data)
} catch (err) {
console.log(err)
}
}
是不是好棒棒?
3. 條件判斷
想象一下這個場景翘紊,一段代碼需要獲取一些數(shù)據(jù)然后決定是返回這些數(shù)據(jù)蔽氨,還是根據(jù)這些數(shù)據(jù)中的一些值來獲取更多的數(shù)據(jù)。
const makeRequest = () => {
return getJSON()
.then(data => {
if (data.needsAnotherRequest) {
return makeAnotherRequest(data)
.then(moreData => {
console.log(moreData)
return moreData
})
} else {
console.log(data)
return data
}
})
}
是不是看著就頭大帆疟?這六層的代碼很容易讓你頭暈?zāi)垦p木俊D切├ㄌ枺约皉eturn語句只是為了將最終結(jié)果傳遞到主要Promise中踪宠。
這個例子用async/await重寫以后可讀性大大提高自赔。
const makeRequest = async () => {
const data = await getJSON()
if (data.needsAnotherRequest) {
const moreData = await makeAnotherRequest(data);
console.log(moreData)
return moreData
} else {
console.log(data)
return data
}
}
4. 中間值
你可能遇到這種情況:你需要調(diào)用promise1
然后用它的返回值去調(diào)用promise2
,然后用兩者的返回值去調(diào)用promise3
柳琢,你的代碼很可能是這樣的:
const makeRequest = () => {
return promise1()
.then(value1 => {
// do something
return promise2(value1)
.then(value2 => {
// do something
return promise3(value1, value2)
})
})
}
如果promise3
不需要value1
绍妨,這個層級關(guān)系會清楚一些。如果你不能忍受柬脸,你可以用promise.all
包裝value1和value2:
const makeRequest = () => {
return promise1()
.then(value1 => {
// do something
return Promise.all([value1, promise2(value1)])
})
.then(([value1, value2]) => {
// do something
return promise3(value1, value2)
})
}
這種寫法犧牲了可讀性他去,value1
和value2
不應(yīng)當出于任何理由屬于同一個數(shù)組。
同樣的邏輯如果使用async/await來寫會變得很簡單倒堕,會讓你懷疑以前為什么掙扎著讓使用Promise
的代碼看起來更簡單:
const makeRequest = async () => {
const value1 = await promise1()
const value2 = await promise2(value1)
return promise3(value1, value2)
}
5. 錯誤棧
想像這樣一個情景:連續(xù)鏈式調(diào)用多個Promise
灾测,然后其中某個地方可能會拋出異常:
const makeRequest = () => {
return callAPromise()
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => {
throw new Error("oops");
})
}
makeRequest()
.catch(err => {
console.log(err);
// 輸出
// Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
})
這個錯誤輸出棧不能明確的指示錯誤發(fā)生在哪里。甚至?xí)a(chǎn)生誤導(dǎo):整個錯誤只包含一個方法名then
垦巴。
然而媳搪,async/await的錯誤棧會指向具體產(chǎn)生錯誤的方法:
const makeRequest = async () => {
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
throw new Error("oops");
}
makeRequest()
.catch(err => {
console.log(err);
// output
// Error: oops at makeRequest (index.js:7:9)
})
其實當你本地開發(fā)調(diào)試時,這一點意義不大骤宣。但是如果你需要在生產(chǎn)環(huán)境服務(wù)器上的代碼上找錯時蛾号,就非常實用了。在這種情況下涯雅,知道錯誤發(fā)生在makeRequest
比知道錯誤發(fā)生在then.then.then.then...
要好很多......
6. 調(diào)試
最后鲜结,async/await一個巨大的優(yōu)勢是它非常容易調(diào)試。對Promise
進行調(diào)試出于兩個原因非常的痛苦:
- 你不能在返回表達式的箭頭函數(shù)上設(shè)置斷點(沒有函數(shù)體)。
const makeRequest = () =>{
reutrn callAPromise()
.then(()=>callAPromise())
.then(()=>callAPromise())
.then(()=>callAPromise())
.then(()=>callAPromise())
}
- 如果你在一個
.then
塊內(nèi)設(shè)置了斷點精刷,然后用step-over
等功能時拗胜,調(diào)試器不會進入.then
代碼因為他只會"step"進同步代碼。
使用async/await你不需要那么多箭頭函數(shù)怒允,你可以直接"step"那些await
調(diào)用埂软,就像普通的同步調(diào)用一樣。
const makeRequest = async () =>{
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
}
結(jié)論
Async/await是Javascript近幾年來最具有革命性的特性之一纫事,它讓你體會到Promise
是多么的混亂勘畔,然后給你一個方便的替代品。