「async/await」是 promises 的另外一種更便捷更流行的寫法,同時它也更易於理解和使用。html
讓咱們以 async
這個關鍵字開始。它能夠被放置在任何函數前面,像下面這樣:git
async function f() { return 1; }
在函數前面的「async」這個單詞表達了一個簡單的事情:即這個函數老是返回一個 promise。即便這個函數在語法上返回了一個非 promise 的值,加了「async」這個關鍵字就會指示 JavaScript 引擎自動將返回值包裝成一個解析後的 promise。github
例如,如下的代碼就返回了一個以 1
爲結果的解析後的 promise, 讓咱們試一下:json
async function f() { return 1; } f().then(alert); // 1
... 咱們也能夠顯式返回一個 promise,結果是同樣的:api
async function f() { return Promise.resolve(1); } f().then(alert); // 1
因此說,async
確保了函數的返回值是一個 promise,也會包裝非 promise 的值。很簡單是吧?可是還沒完。還有一個關鍵字叫 await
,它只在 async
函數中有效,也很是酷。promise
語法以下:app
// 只在 async 函數中有效 let value = await promise;
關鍵字 await
讓 JavaScript 引擎等待直到 promise 完成並返回結果。異步
這裏的例子就是一個 1 秒後解析的 promise:async
async function f() { let promise = new Promise((resolve, reject) => { setTimeout(() => resolve("done!"), 1000) }); let result = await promise; // 等待直到 promise 解析 (*) alert(result); // "done!" } f();
這個函數在執行的時候,「暫停」在了 (*)
那一行,而且當 promise 完成後,拿到 result
做爲結果繼續往下執行。因此「done!」是在一秒後顯示的。函數
劃重點:await
字面的意思就是讓 JavaScript 引擎等待直到 promise 狀態完成,而後以完成的結果繼續執行。這個行爲不會耗費 CPU 資源,由於引擎能夠同時處理其餘任務:執行其餘腳本,處理事件等。
相比 promise.then
來獲取 promise 結果,這只是一個更優雅的語法,同時也更易書寫。
不能在普通函數中使用 await
若是咱們嘗試在非 async 函數中使用 await
的話,就會報語法錯誤:
function f() { let promise = Promise.resolve(1); let result = await promise; // 語法錯誤 }
若是函數前面沒有 async
關鍵字,咱們就會獲得一個語法錯誤。就像前面說的,await
只在 async 函數
中有效。
讓咱們拿 Promises 鏈那一章的 showAvatar()
例子改寫成 async/await
的形式:
await
替換掉 .then
的調用async
關鍵字async function showAvatar() { // 讀取 JSON let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json(); // 讀取 github 用戶信息 let githubResponse = await fetch(`https://api.github.com/users/${user.name}`); let githubUser = await githubResponse.json(); // 顯示頭像 let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img); // 等待 3 秒 await new Promise((resolve, reject) => setTimeout(resolve, 3000)); img.remove(); return githubUser; } showAvatar();
簡潔明瞭,是吧?比以前可強多了。
await
不能在頂層代碼運行
剛開始使用 await
的人經常會忘記 await
不能用在頂層代碼中。如,下面這樣就不行:
// 用在頂層代碼中會報語法錯誤 let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json();
咱們能夠將其包裹在一個匿名 async 函數中,如:
(async () => { let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json(); ... })();
await
能夠接收「thenables」
像 promise.then
那樣,await
被容許接收 thenable 對象(具備 then
方法的對象)。有些對象雖然不是 promise,可是卻兼容 promise,若是這些對象支持 .then
,那麼就能夠對它們使用 await
。
下面是一個 Thenable
類,await
接收了該類的實例:
class Thenable { constructor(num) { this.num = num; } then(resolve, reject) { alert(resolve); // 1 秒後解析爲 this.num*2 setTimeout(() => resolve(this.num * 2), 1000); // (*) } }; async function f() { // 等待 1 秒, result 變爲 2 let result = await new Thenable(1); alert(result); } f();
若是 await
接收了一個非 promise 的可是提供了 .then
方法的對象,它就會調用這個 then 方法,並將原生函數 resolve
,reject
做爲參數傳入。而後 await
等到這兩個方法中的某個被調用(在例子中發生在(*)的那一行),再處理獲得的結果。
Async methods
若是想定義一個 async 的類方法,在方法前面添加 async
就能夠了:
class Waiter { async wait() { return await Promise.resolve(1); } } new Waiter() .wait() .then(alert); // 1
若是一個 promise 正常解析,await promise
返回的就是其結果。可是若是 promise 被拒絕,就會拋出一個錯誤,就像在那一行有個 throw
語句那樣。
這裏的代碼:
async function f() { await Promise.reject(new Error("Whoops!")); }
...和下面是同樣的:
async function f() { throw new Error("Whoops!"); }
在真實的環境下,promise 被拒絕前一般會等待一段時間。因此 await
會等待,而後拋出一個錯誤。
咱們能夠用 try...catch
來捕獲上面的錯誤,就像對通常的 throw
語句那樣:
async function f() { try { let response = await fetch('http://no-such-url'); } catch(err) { alert(err); // TypeError: failed to fetch } } f();
若是有錯誤發生,代碼就會跳到 catch
塊中。固然也能夠用 try 包裹多行 await 代碼:
async function f() { try { let response = await fetch('/no-user-here'); let user = await response.json(); } catch(err) { // 捕獲到 fetch 和 response.json 中的錯誤 alert(err); } } f();
若是咱們不使用 try...catch
,由f()
產生的 promise 就會被拒絕。咱們能夠在函數調用後添加 .catch
來處理錯誤:
async function f() { let response = await fetch('http://no-such-url'); } // f() 變爲一個被拒絕的 promise f().catch(alert); // TypeError: failed to fetch // (*)
若是咱們忘了添加 .catch
,咱們就會獲得一個未處理的 promise 錯誤(顯示在控制檯)。咱們能夠經過在錯誤處理與 Promise 章節講的全局事件處理器來捕獲這些。
async/await
和 promise.then/catch
當咱們使用 async/await
時,幾乎就不會用到 .then
了,由於爲咱們await
處理了異步等待。而且咱們能夠用 try...catch
來替代 .catch
。這一般更加方便(固然不是絕對的)。
可是當咱們在頂層代碼,外面並無任何 async
函數,咱們在語法上就不能使用 await
了,因此這時候就能夠用 .then/catch
來處理結果和異常。
就像上面代碼的 (*)
那行同樣。
async/await
能夠和 Promise.all
一塊兒使用
當咱們須要同時等待多個 promise 時,咱們能夠用 Promise.all
來包裹他們,而後使用 await
:
// 等待多個 promise 結果 let results = await Promise.all([ fetch(url1), fetch(url2), ... ]);
若是發生錯誤,也會正常傳遞:先從失敗的 promise 傳到 Promise.all
,而後變成咱們能用 try...catch
處理的異常。
咱們在微任務和事件循環章節講過,promise 回調是異步執行的。每一個 .then/catch/finally
回調首先被放入「微任務隊列」而後在當前代碼執行完成後被執行。
Async/await
是基於 promise 的,因此它內部使用相同的微任務隊列,而且相對宏任務來講具備更高的優先級。
例如,看代碼:
setTimeout(handler, 0)
,應該以零延遲運行 handler
函數。let x = await f()
,函數 f()
是異步的,可是會當即運行。那麼若是 await
在 setTimeout
下面,哪個先執行呢?
async function f() { return 1; } (async () => { setTimeout(() => alert('timeout'), 0); await f(); alert('await'); })();
這裏很肯定:await
老是先完成,由於(做爲微任務)它相比 setTimeout
具備更高的優先級。
函數前面的關鍵字 async
有兩個做用:
await
這個 await
關鍵字又讓 JavaScript 引擎等待直到 promise 完成,而後:
throw error
語句同樣。這兩個關鍵字一塊兒用就提供了一個很棒的方式來控制異步代碼,而且易於讀寫。
有了 async/await
咱們就幾乎不須要使用 promise.then/catch
,可是不要忘了它們是基於 promise 的,因此在有些時候(如在最外層代碼)咱們就能夠用 promise 的形式。再有就是 Promise.all
能夠幫助咱們同時處理多個異步任務。