自從Node的7.6版本,已經(jīng)默認(rèn)支持async/await特性了逆趣。如果你還沒有使用過他蝶溶,或者對他的用法不太了解,這篇文章會(huì)告訴你為什么這個(gè)特性“不容錯(cuò)過”宣渗。本文輔以大量實(shí)例抖所,相信你能很輕松的看懂,并了解Javascript處理異步的一大殺器痕囱。
文章靈感和內(nèi)容借鑒了6 Reasons Why JavaScript’s Async/Await Blows Promises Away (Tutorial)田轧,英文好的同學(xué)可以直接戳原版參考。
初識(shí)Async/await
對于還不了解Async/await特性的同學(xué)鞍恢,下面一段是一個(gè)“速成”培訓(xùn)傻粘。
Async/await 是Javascript編寫異步程序的新方法。以往的異步方法無外乎回調(diào)函數(shù)和Promise帮掉。但是Async/await建立于Promise之上弦悉。對于Javascript處理異步,是個(gè)老生常談卻歷久彌新的話題:
從最早的回調(diào)函數(shù)旭寿,到 Promise 對象警绩,再到 Generator 函數(shù),每次都有所改進(jìn)盅称,但又讓人覺得不徹底肩祥。它們都有額外的復(fù)雜性,都需要理解抽象的底層運(yùn)行機(jī)制缩膝。
異步編程的最高境界混狠,就是根本不用關(guān)心它是不是異步。
async 函數(shù)就是隧道盡頭的亮光疾层,很多人認(rèn)為它是異步操作的終極解決方案将饺。
Async/await語法
試想一下,我們有一個(gè)getJSON方法,該方法發(fā)送一個(gè)異步請求JSON數(shù)據(jù)予弧,并返回一個(gè)promise對象刮吧。這個(gè)promise對象的resolve方法傳遞異步獲得的JSON數(shù)據(jù)。具體例子的使用如下:
const makeRequest = () =>
getJSON()
.then(data => {
console.log(data)
return "done"
})
makeRequest()
在使用async/await時(shí)掖蛤,寫法如下:
const makeRequest = async () => {
console.log(await getJSON())
return "done"
}
makeRequest()
對比兩種寫法杀捻,針對第二種,我需要進(jìn)一步說明:
1)第二種寫法(使用async/await)蚓庭,在主體函數(shù)之前使用了async關(guān)鍵字致讥。在函數(shù)體內(nèi),使用了await關(guān)鍵字器赞。當(dāng)然await關(guān)鍵字只能出現(xiàn)在用async聲明的函數(shù)體內(nèi)垢袱。該函數(shù)會(huì)隱式地返回一個(gè)Promise對象,函數(shù)體內(nèi)的return值港柜,將會(huì)作為這個(gè)Promise對象resolve時(shí)的參數(shù)请契。
可以使用then方法添加回調(diào)函數(shù)。當(dāng)函數(shù)執(zhí)行的時(shí)候潘懊,一旦遇到await就會(huì)先返回姚糊,等到異步操作完成贿衍,再接著執(zhí)行函數(shù)體內(nèi)后面的語句授舟。
2)示例中,await getJSON() 說明console.log的調(diào)用贸辈,會(huì)等到getJSON()返回的promise對象resolve之后觸發(fā)释树。
我們在看一個(gè)例子加強(qiáng)一下理解,該例子取自阮一峰大神的《ECMAScript 6 入門》一書:
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
上面代碼指定50毫秒以后擎淤,輸出hello world奢啥。
Async/await究竟好在哪里?
那么嘴拢,同樣是處理異步操作桩盲,Async/await究竟好在哪里呢?
我們總結(jié)出以下6點(diǎn)席吴。
簡約而干凈Concise and clean
我們看一下上面兩處代碼的代碼量九串,就可以直觀地看出使用Async/await對于代碼量的節(jié)省是很明顯的堡掏。對比Promise,我們不需要書寫.then,不需要新建一個(gè)匿名函數(shù)處理響應(yīng)涯呻,也不需要再把數(shù)據(jù)賦值給一個(gè)我們其實(shí)并不需要的變量。同樣文留,我們避免了耦合的出現(xiàn)题禀。這些看似很小的優(yōu)勢其實(shí)是很直觀的,在下面的代碼示例中,將會(huì)更加放大撕捍。
錯(cuò)誤處理Error handling
Async/await使得處理同步+異步錯(cuò)誤成為了現(xiàn)實(shí)拿穴。我們同樣使用try/catch結(jié)構(gòu),但是在promises的情況下忧风,try/catch難以處理在JSON.parse過程中的問題贞言,原因是這個(gè)錯(cuò)誤發(fā)生在Promise內(nèi)部。想要處理這種情況下的錯(cuò)誤阀蒂,我們只能再嵌套一層try/catch该窗,就像這樣:
const makeRequest = () => {
try {
getJSON()
.then(result => {
// this parse may fail
const data = JSON.parse(result)
console.log(data)
})
// uncomment this block to handle asynchronous errors
// .catch((err) => {
// console.log(err)
// })
}
catch (err) {
console.log(err)
}
}
但是,如果用async/await處理蚤霞,一切變得簡單酗失,解析中的錯(cuò)誤也能輕而易舉的解決:
const makeRequest = async () => {
try {
// this parse may fail
const data = JSON.parse(await getJSON())
console.log(data)
}
catch (err) {
console.log(err)
}
}
條件判別Conditionals
想象一下這樣的業(yè)務(wù)需求:我們需要先拉取數(shù)據(jù),然后根據(jù)得到的數(shù)據(jù)判斷是否輸出此數(shù)據(jù)昧绣,或者根據(jù)數(shù)據(jù)內(nèi)容拉取更多的信息规肴。如下:
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
}
})
}
這樣的代碼會(huì)讓我們看的頭疼。這這么多層(6層)嵌套過程中夜畴,非常容易“丟失自我”拖刃。
使用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
}
}
中間值Intermediate values
一個(gè)經(jīng)常出現(xiàn)的場景是贪绘,我們先調(diào)起promise1兑牡,然后根據(jù)返回值,調(diào)用promise2税灌,之后再根據(jù)這兩個(gè)Promises得值均函,調(diào)取promise3。使用Promise菱涤,我們不難實(shí)現(xiàn):
const makeRequest = () => {
return promise1()
.then(value1 => {
// do something
return promise2(value1)
.then(value2 => {
// do something
return promise3(value1, value2)
})
})
}
如果你難以忍受這樣的代碼苞也,我們可以優(yōu)化我們的Promise,方案是使用Promise.all來避免很深的嵌套粘秆。
就像這樣:
const makeRequest = () => {
return promise1()
.then(value1 => {
// do something
return Promise.all([value1, promise2(value1)])
})
.then(([value1, value2]) => {
// do something
return promise3(value1, value2)
})
}
Promise.all這個(gè)方法犧牲了語義性如迟,但是得到了更好的可讀性。
但是其實(shí)攻走,把value1 & value2一起放到一個(gè)數(shù)組中殷勘,是很“蛋疼”的,某種意義上也是多余的陋气。
同樣的場景劳吠,使用async/await會(huì)非常簡單:
const makeRequest = async () => {
const value1 = await promise1()
const value2 = await promise2(value1)
return promise3(value1, value2)
}
錯(cuò)誤堆棧信息Error stacks
想象一下我們鏈?zhǔn)秸{(diào)用了很多promises,一級(jí)接一級(jí)巩趁。緊接著痒玩,這條promises鏈中某處出錯(cuò)淳附,如下:
const makeRequest = () => {
return callAPromise()
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => {
throw new Error("oops");
})
}
makeRequest()
.catch(err => {
console.log(err);
// output
// Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
})
此鏈條的錯(cuò)誤堆棧信息并沒用線索指示錯(cuò)誤到底出現(xiàn)在哪里。更糟糕的事蠢古,他還會(huì)誤導(dǎo)開發(fā)者:錯(cuò)誤信息中唯一出現(xiàn)的函數(shù)名稱其實(shí)根本就是無辜的奴曙。
我們再看一下async/await的展現(xià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ā)階段區(qū)別不是很大草讶。但是想象一下在服務(wù)器端洽糟,線上代碼的錯(cuò)誤日志情況下,將會(huì)變得非常有意義堕战。你一定會(huì)覺得上面這樣的錯(cuò)誤信息坤溃,比“錯(cuò)誤出自一個(gè)then的then的then。嘱丢。薪介。”有用的多越驻。
調(diào)試Debugging
最后一點(diǎn)汁政,但是也是很重要的一點(diǎn),使用async/await來debug會(huì)變得非常簡單缀旁。
在一個(gè)返回表達(dá)式的箭頭函數(shù)中记劈,我們不能設(shè)置斷點(diǎn),這就會(huì)造成下面的局面:
const makeRequest = () => {
return callAPromise()
.then(()=>callAPromise())
.then(()=>callAPromise())
.then(()=>callAPromise())
.then(()=>callAPromise())
}
我們無法在每一行設(shè)置斷點(diǎn)并巍。但是使用async/await時(shí):
const makeRequest = async () => {
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
}
總結(jié)
Async/await是近些年來JavaScript最具革命性的新特性之一目木。他讓讀者意識(shí)到使用Promise存在的一些問題,并提供了自身來代替Promise的方案履澳。
當(dāng)然嘶窄,對這個(gè)新特性也有一定的擔(dān)心怀跛,體現(xiàn)在:
他使得異步代碼變的不再明顯距贷,我們好不容易已經(jīng)學(xué)會(huì)并習(xí)慣了使用回調(diào)函數(shù)或者.then來處理異步。新的特性當(dāng)然需要時(shí)間成本去學(xué)習(xí)和體會(huì)吻谋;
退回來說忠蝗,熟悉C#語言的程序員一定會(huì)懂得這些學(xué)習(xí)成本是完全值得的。
Happy Coding!
PS: 作者Github倉庫漓拾,歡迎通過代碼各種形式交流阁最。