JavaScript Promise 是替代傳統回撥函式的一個方案,是回撥函式的一個改進版。但使用 Promise 會讓程式碼中大量出現 then 方法,一長串的那種。ES2017 引入了一種新的處理非同步任務的方式----async 函式,它比使用 Promise API 更加簡潔。
引入的 async 函式,使用了兩個新的關鍵字:async 和 await。
快速預覽
- async 關鍵字在函式宣告前使用。
- await 用於處理 Promise 物件。
- await 只能用在 async 函式中。
- async 函式總是返回一個 Promise 物件,不論函式是否 return Promise 物件。
- async/await 和 Primose 物件在本質上是一樣的。
使用 async 和 await 的好處
- 程式碼更加簡潔、精確。
- 因為少回撥,Debug 起來更容易。
- 從 Promise then/catch 書寫形式過渡過來非常自然。
- 程式碼更加“自上而下”,少巢狀。
async 和 await 的基本使用
一例勝千言,先看一個簡單的使用 async 和 await 的例子。
// 將函式宣告為一個 async 函式,這樣就能在內部使用 await 了
async function fetchContent() {
// 使用 await,而非 fetch.then
let content = await fetch('/');
let text = await content.text();
// async 函式最終返回一個 resolved 狀態的 Promise 物件,
// Promise 物件的 then 回撥方法接收的引數就是這裡的 text
return text;
}
// 呼叫 async 函式
let promise = fetchContent.then(...);
複製程式碼
async 函式以 async 關鍵字標記,await 只能用在 async 函式中。await 後面緊跟的是生成 Promise 物件的(promise-yielding)操作,對應這裡的 fetch API。只有等到第一個 await 後面的操作完成後,才會繼續執行後面的程式碼。最終,async 函式返回一個 resolved 狀態的 Promise 物件,而這個 Promise 物件的 then 回撥方法中,接收的引數就是 text。
從 Promise 過渡到 async 函式
讓我們看一下,怎麼將一個 Promise 例子改寫成 async 函式的形式。
// 之前:回撥城!
fetch('/users.json')
.then(response => response.json())
.then(json => {
console.log(json);
})
.catch(err = {
console.log(err);
});
// 之後:不再有任何回撥!
async function getJson() {
try {
let response = await fetch('/users.json');
let json = await response.json();
console.log(json);
} catch (err) {
console.log(err);
}
}
複製程式碼
是不是程式碼變簡潔、好看了呢。
玩轉 async 函式
下面介紹了幾種使用 async 函式的場景和方式。
匿名 async 函式
let main = (async function () {
return await fetch('/');
})();
複製程式碼
async 函式宣告
async function main() {
return await fetch('/');
}
複製程式碼
async 函式表示式
let main = async function () {
return await fetch('/');
};
// 也可以使用箭頭函式哦!
let main = async () => {
return fetch('/');
};
複製程式碼
當做引數的 async 函式
document.body.addEventListener('click', async function () {
return await fetch('/');
});
複製程式碼
作為物件和類的方法
// 作為物件方法
let obj = {
async method() {
return await fetch('/');
}
};
// 作為類方法
class MyClass {
async method() {
return await fetch('/');
}
}
複製程式碼
你看到了,async 函式除了自身提供的超炫功能外,TM 跟普通函式使用起來是一樣一樣的!
錯誤處理
傳統 rejected 狀態的 Promise 物件使用 catch 方法捕獲錯誤。而 await 相當於是已經處理了 resolved 狀態下 Promise 物件返回的資料,所以在處理錯誤時,await 藉助了 try/catch:
try {
let x = await myAsyncFunction();
} catch (err) {
// Error!
}
複製程式碼
雖然,try/catch 處理錯誤的方式看起來很老套,但是相對於引入 await 給我們帶來的便捷,這根本算不上什麼(要不然還要怎樣)。
等待平行任務
Google 的 Jake Archibald 在 async functions 文件 提出了一個很好的建議----不要同時平行地使用多個 await 語句,這會導致等待時間層層累加,如果可能的話,應該立即發出非同步任務,之後再使用 await 等待任務完成(會節省時間的呦)。
// 一共要花掉 1000ms!
async function series() {
await wait(500);
await wait(500);
return "done!";
}
// 僅花掉 500ms!
async function parallel() {
const wait1 = wait(500);
const wait2 = wait(500);
await wait1;
await wait2;
return "done!";
}
複製程式碼
第二種情況讓兩個非同步請求同時發出,第一個請求在 500ms 後結束後,輪到第二個請求的時候,也已經完成並立即就能返回結果。這種情況適應於無依賴的請求之間。
await Promise.all
我最喜歡的 Promise API 的功能之一就是 Promise.all
,它會等待所有的 Promise 物件完成之後再處理資料。我們也可以在 Promise.all
上使用 await:
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
複製程式碼
記住,async/await 和 Primose 物件在本質上是一樣的,這是我們能夠使用 await Promise.all 等待多個 resolved 狀態 Promise 物件返回資料的原因。
使用 Promise 介面程式設計仍然很優秀,但是相比於 async 和 await,在維護性上略輸一籌。
(完)