與直接使用 Promise
相比,使用 async/await
不只能夠使代碼更具可讀性,並且還能夠在 JavaScript 引擎中實現一些有趣的優化。html
這篇文章是關於一個這樣的優化,涉及異步代碼的堆棧追蹤。promise
async/await
和 Promise
的根本區別在於 await fn()
暫停當前函數的執行,而 promise.then(fn)
在將 fn
調用添加到回調鏈後,繼續執行當前函數。babel
const fn = () => console.log('hello') const a = async () => { await fn() // 暫停 fn 的執行 } // 調用 a 時,才恢復 fn 的執行 a() // "hello" const promise = Promise.resolve() // 將 fn 添加到回調鏈後,繼續執行 fn promise.then(fn) // "hello"
在堆棧追蹤的上下文中,這種差別很是顯著。異步
當一個 Promise
鏈(不管是否脫糖化)在任什麼時候候拋出一個未經處理的異常時, JavaScript 引擎都會顯示一條錯誤信息和(但願)記錄一個有用的堆棧追蹤。async
做爲一名開發人員,不管您使用的是普通的 Promise
仍是 async await
,您都會指望這樣。函數
想象一個場景,當對異步函數 b
的調用解析時,調用函數 c
:性能
const b = () => Promise.resolve() const a = () => { b().then(() => c()) }
當調用 a
時,將同步發生如下狀況:優化
b
被調用並返回一個 Promise
,該 Promise
將在未來某個時刻解決。.then
回調(其實是調用 c()
)被添加到回調鏈中( V8 術語中,[…]被添加爲解析處理程序)。以後,咱們完成了在函數 a
的主體中執行代碼。a
永遠不會被掛起,當對 b
的異步調用解析時,上下文已經消失了。code
想象一下若是 b
(或 c
)異步拋出異常會發生什麼?理想狀況下,堆棧追蹤應該包括 a
,由於 b
(或 c
)是從那裏調用的,對吧?既然咱們不在參考 a
了 ,那怎樣能作到呢?htm
爲了讓它工做,JavaScript 引擎須要在上面的步驟以外作一些事情:它在有機會的時候捕獲並存儲堆棧追蹤。
在 V8 中,堆棧追蹤附加到 b
返回的 Promise
。當 Promise
實現時,堆棧追蹤將被傳遞,以便 c
能夠根據須要使用它。
b()[a] -> b().then()[a] -> c[a?:a]
捕獲堆棧追蹤須要時間(即下降性能);存儲這些堆棧追蹤須要內存。
下面是一樣的程序,使用 async/await
而不是 Promise
編寫:
const b = () => Promise.resolve() const a = async () => { await b() c() }
使用 await
,即便在 await
調用中不收集堆棧追蹤,咱們也能夠恢復調用鏈。
這是可能的,由於 a
被掛起,正在等待 b
解決。若是 b
拋出異常,則能夠按需以這種方式重建堆棧追蹤。
若是 c
拋出異常,堆棧追蹤能夠像同步函數那樣構造,由於發生這種狀況時,咱們仍在 a
上下文中。
經過遵循如下建議,使 JavaScript 引擎可以以更高效的方式處理堆棧追蹤:
async/await
賽過 Promise
。async/await
傳輸。