上次我們大概說了一下 Promise 晋被,今天就接著講 async/await 這組 API 。
async/await 是 ES7 的標(biāo)準(zhǔn)刚盈,Promise 是 ES6 標(biāo)準(zhǔn)羡洛,async/await 這套 API 也是用來幫助我們寫異步代碼的,它是構(gòu)建在 Promise 之上的藕漱,有點(diǎn)像 Okhttp 和 Retrofit 的關(guān)系欲侮。
什么是 async ?
async function myFirstAsyncFunction() {
try {
const fulfilledValue = await doSomeThing();
}
catch (rejectedValue) {
// …
}
}
大概長上面這個樣子谴分。async 一般不單獨(dú)使用锈麸,而是和 await 一起使用,一個 async 函數(shù)內(nèi)部可能有 零個 或者 多個 await 牺蹄。
這段代碼即使沒有學(xué)過 Promise 也很容易看懂忘伞。這就是 async 函數(shù)的優(yōu)勢所在。
async 函數(shù)被調(diào)用的時候,會立即返回一個 Promise氓奈。
await
await 不能單獨(dú)使用翘魄,如果在非 async 函數(shù)內(nèi)部被調(diào)用會報錯。await 后面一般跟一個 Promise 舀奶,也可以是其他的暑竟,比如一個數(shù)值,或者一個變量育勺,或者一個函數(shù)但荤。如果 await 后面不是一個 Promise 就會返回一個已經(jīng) resolve 的 Promise。
當(dāng) async 函數(shù)執(zhí)行到 await 的時候涧至,會暫停整個async函數(shù)的執(zhí)行進(jìn)程并出讓其控制權(quán)腹躁,只有當(dāng)其等待的基于Promise 的異步操作被兌現(xiàn)或被拒絕之后才會恢復(fù)進(jìn)程。
當(dāng)然南蓬,async 函數(shù)也會返回一個 Promise 纺非,也就是說,await 后面也可以跟一個 async 函數(shù)赘方。
async/await 這套 API 烧颖,感覺有 Kotlin 中的 協(xié)程 和 掛起函數(shù) 內(nèi)味。
為什么要使用 async 窄陡?
隱藏 Promise 炕淮,更易于理解
假設(shè)我們想請求一個接口,然后把響應(yīng)的數(shù)據(jù)打印出來泳梆,并且捕獲異常鳖悠。用 Promise 大概是這樣寫:
function logFetch(url) {
return fetch(url)
.then(response => response.text())
.then(text => {
console.log(text);
}).catch(err => {
console.error('fetch failed', err);
});
}
如果用 async 函數(shù)來寫,大概是這個樣子:
async function logFetch(url) {
try {
const response = await fetch(url);
console.log(await response.text());
}
catch (err) {
console.log('fetch failed', err);
}
}
雖然代碼的行數(shù)差不多优妙,但是代碼看起來更加簡潔乘综,少了很多 then 的嵌套。請求一個接口數(shù)據(jù)套硼,然后打印卡辰,就像你看到的,很簡單邪意。
就像剛才的那個例子九妈,如果沒學(xué)過 Promise 也不影響你看懂和理解 async 代碼。這個好像沒什么雾鬼,但是對于一個大型的項目萌朱,人員的技術(shù)水平參差不齊,不能保證所有人都熟悉和理解 Promise 策菜,我們要做的是盡可能的降低代碼的理解難度晶疼,使大部分人都能看懂酒贬。
用同步的思路寫異步邏輯
async/await 最大的優(yōu)勢就是我們可以用同步的思路來寫異步的業(yè)務(wù)邏輯,所以代碼整體看起來更加容易看懂翠霍。我們舉個例子锭吨。
上面的代碼還是比較簡單,我們再舉一個復(fù)雜一點(diǎn)的例子寒匙。
我們想獲取一個網(wǎng)絡(luò)資源的大小零如,如果使用 Promise 大概可能是這個樣子:
function getResponseSize(url) {
return fetch(url).then(response => {
const reader = response.body.getReader();
let total = 0;
return reader.read().then(function processResult(result) {
if (result.done) return total;
const value = result.value;
total += value.length;
console.log('Received chunk', value);
return reader.read().then(processResult);
})
});
}
即使你學(xué)過 Promise ,這個代碼也并不是很好理解锄弱,更別說是沒有學(xué)過 Promise 的同學(xué)了考蕾。因為中間有一個循環(huán)的過程,而且這個執(zhí)行的過程的異步的会宪,并不想我們之前學(xué)到的一個鏈?zhǔn)秸{(diào)用能解決的辕翰。當(dāng)然,你也可以抽取一下狈谊,使代碼更簡潔一點(diǎn)。
const processResult = (result) =>{
if (result.done) return total;
const value = result.value;
total += value.length;
console.log('Received chunk', value);
return reader.read().then(processResult);
}
function getResponseSize(url) {
return fetch(url).then(response => {
const reader = response.body.getReader();
let total = 0;
return reader.read().then(processResult)
});
}
但是多了一個方法沟沙,勉勉強(qiáng)強(qiáng)吧河劝。但是我們看一下 async 函數(shù)是怎么處理的。
async function getResponseSize(url) {
const response = await fetch(url);
const reader = response.body.getReader();
let result = await reader.read();
let total = 0;
while (!result.done) {
const value = result.value;
total += value.length;
console.log('Received chunk', value);
// get the next result
result = await reader.read();
}
return total;
}
OHHHHHHHHH
這個是不是看起來就更加流暢了矛紫?因為 await 表達(dá)式會阻塞運(yùn)行赎瞎,甚至可以直接阻塞循環(huán),所以整體看起來像同步的代碼颊咬,也更符合直覺务甥,更容易讀懂這個代碼。
小心 await 阻塞
由于 await 能夠阻塞 async 函數(shù)的運(yùn)行喳篇,所以代碼看起來更像同步的代碼敞临,更容易閱讀和理解。但是要小心 await 阻塞麸澜,因為有些阻塞是不必要的挺尿,不恰當(dāng)使用可能會影響代碼的性能。
假如我們要把一個網(wǎng)絡(luò)數(shù)據(jù)和本地數(shù)據(jù)合并炊邦,錯誤的實例可能是這樣子:
async function combineData(url, file) {
let networkData = await fetch(url)
let fileData = await readeFile(file)
console.log(networkData + fileData)
}
其實我們不用等一個文件讀完了编矾,再去讀下個文件,我們可以兩個文件一起讀馁害,讀完之后再進(jìn)行合并窄俏,這樣能提高代碼的運(yùn)行速度。我們可以這樣寫:
async function combineData(url, file) {
let fetchPromise = fetch(url)
let readFilePromise = readFile(file)
let networkData = await fetchPromise
let fileData = await readFilePromise
console.log(networkData + fileData)
}
這樣的話碘菜,就可以同時 網(wǎng)絡(luò)請求 和 讀取文件 了凹蜈,可以節(jié)省很多時間限寞。這里主要是利用了 Promise 一旦創(chuàng)建就立刻執(zhí)行的特點(diǎn),不懂的同學(xué)可以復(fù)習(xí)一下上一節(jié)的內(nèi)容踪区。
當(dāng)然昆烁,如果你熟悉 Promise 的話,可以直接使用 Promise.all 的方式來處理缎岗,或者 await 后面跟 Promise.all 這里就不展開講了静尼。
異常處理
try...catch
在 async 函數(shù)中,異常處理一般是 try...catch 传泊,如果沒有進(jìn)行 try...catch 鼠渺,await 表達(dá)式一旦 reject ,async 函數(shù)返回的 Promise 就會 reject 眷细。
其實結(jié)合 Promise 來看拦盹,如果一個 Promise 狀態(tài)敲定為 reject ,并且后續(xù)的 then 沒有傳入 reject 函數(shù)溪椎,或者沒有 catch 普舆,那么就會拋出異常。從這個角度來看校读,在 async 函數(shù)中用 try...catch 來包住 await 表達(dá)式沼侣,可能就是 catch 住這個異常,并且把這個 reject 信息傳到 catch 里面歉秫。
這里就不舉例子了蛾洛。