JS異步堆棧追蹤之為什麼await勝過Promise
概述
async/await和Promise的根本區別在於await fn()暫停當前函數的執行,而promise.then(fn)在將fn調用添加到回調鏈後,繼續執行當前函數。
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引擎都會顯示一條錯誤信息和(希望)記錄一個有用的堆棧追蹤。
作為一名開發人員,無論您使用的是普通的Promise還是async await,您都會期望這樣。
Promise
想象一個場景,當對異步函數b的調用解析時,調用函數c:
const b = () => Promise.resolve() const a = () => { b().then(() => c()) }
當調用a時,將同步發生以下情況:
- b被調用並返回一個Promise,該Promise將在將來某個時刻解決。
- .then回調(實際上是調用c())被添加到回調鏈中( V8 術語中,[…]被添加為解析處理程序)。
之後,我們完成瞭在函數a的主體中執行代碼。a永遠不會被掛起,當對b的異步調用解析時,上下文已經消失瞭。
想象一下如果b(或c)異步拋出異常會發生什麼?理想情況下,堆棧追蹤應該包括a,因為b(或c)是從那裡調用的,對吧?既然我們不在參考a瞭 ,那怎樣能做到呢?
為瞭讓它工作,JavaScript 引擎需要在上面的步驟之外做一些事情:它在有機會的時候捕獲並存儲堆棧追蹤。
在V8中,堆棧追蹤附加到b返回的Promise。當Promise實現時,堆棧追蹤將被傳遞,以便c可以根據需要使用它。
b()[a] -> b().then()[a] -> c[a?:a]
捕獲堆棧追蹤需要時間(即降低性能);存儲這些堆棧追蹤需要內存。
async/await
下面是同樣的程序,使用async/await而不是Promise編寫:
const b = () => Promise.resolve() const a = async () => { await b() c() }
使用await,即使在await調用中不收集堆棧追蹤,我們也可以恢復調用鏈。
這是可能的,因為a被掛起,正在等待b解決。如果b拋出異常,則可以按需以這種方式重建堆棧追蹤。
如果c拋出異常,堆棧追蹤可以像同步函數那樣構造,因為發生這種情況時,我們仍在a上下文中。
通過遵循以下建議,使 JavaScript 引擎能夠以更高效的方式處理堆棧追蹤:
- 偏好async/await勝過Promise。
- 使用 @babel/preset env避免不必要的async/await傳輸。
以上就是JS異步堆棧追蹤之為什麼await勝過Promise的詳細內容,更多關於Javascript的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- es7中的async、await使用方法示例詳解
- JavaScript詳解使用Promise處理回調地獄與async await修飾符
- JS中的async與await怎麼使用
- 前端常見面試題之async/await和promise的區別
- JS異步代碼單元測試之神奇的Promise