1. 程式人生 > >從JS引擎理解Await b()與Promise.then(b)的堆疊處理

從JS引擎理解Await b()與Promise.then(b)的堆疊處理

譯者按: Async/Await真的只是簡單的語法糖嗎?No!

為了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原作者所有,翻譯僅用於學習。

與直接使用Promise相比,使用Async/Await不僅可以提高程式碼的可讀性,同時也可以優化JavaScript引擎的執行方式。這篇部落格將介紹Async/Await是如何優化JavaScript引擎對堆疊資訊的處理。

Async/Await與Promise最大區別在於:await b()會暫停所在的async函式的執行;而Promise.then(b)將b函式加入回撥鏈中之後,會繼續執行當前函式。對於堆疊來說,這個不同點非常關鍵。

當一個Promise鏈丟擲一個未處理的錯誤時,無論我們使用await b()還是Promise.then(b),JavaScript引擎都需要列印錯誤資訊及其堆疊。對於JavaScript引擎來說,兩者獲取堆疊的方式是不同的。

Promise.then(b)

示例程式碼中,函式c()會在非同步函式b()成功resolve之後執行:

const a = () => {
    b().then(() => c());
};
當呼叫a()函式時,這些事情同步發生: - b()函式被呼叫,它會返回一個Promise,這個Promise會在未來的某個時刻resolve。 - .then()回撥函式(實際呼叫了c()函式)被新增到回撥鏈。 這樣,a()函式內的程式碼就執行完了。a()函式不會被暫停,因此在非同步函式b()resolve時,a()函式的作用域已經不存在了。假設b()或者c()丟擲了一個錯誤,則堆疊資訊中應該包含a()函式,因為它們都是在a()函式內被呼叫。對a()函式的任何引用都不存在了,要如何生成包含a()的堆疊資訊呢? 為了解決這個問題,JavaScript引擎需要做一些額外的工作:它會及時記錄並且儲存堆疊資訊。對於V8引擎來說,堆疊資訊附加在了b()函式所返回的Promise並在Promise鏈中傳遞,這樣c()函式也能在需要的時候獲取堆疊資訊。 記錄堆疊資訊需要時間,這樣會降低效能;而儲存堆疊資訊需要佔用額外的記憶體。 *使用[Fundebug](https://www.fundebug.com/), 可以實時監控線上應用的錯誤,並獲取完整的堆疊資訊。*

Await b()

我們可以使用Async/Await實現同樣的程式碼,同步函式c()會等到非同步函式b()執行結束之後再執行:

const a = async () => {
    await b();
    c();
};
使用await時,無需儲存當前的堆疊資訊,因為儲存b()到a()的指標就足夠了。當等待b()函式執行時,a()函式被暫停了,因此a()函式的作用域還在記憶體可以訪問。如果b()函式丟擲一個錯誤,堆疊資訊可以通過指標迅速生成。如果c()函式丟擲一個錯誤,堆疊資訊也可以像同步函式一樣生成,因為c()函式是在a()函式中執行的。不論是b()還是c(),我們都不需要去儲存堆疊資訊,因為堆疊資訊可以在需要的時候立即生成。而儲存指標,顯然比儲存堆疊更加節省記憶體。

建議

很多ECMAScript語法特性看起來都只是語法糖,其實並非如此,至少Async/Await絕不僅僅只是語法糖。

為了讓JavaScript引擎處理堆疊的方式效能更高並且更加節省記憶體,請遵循這些建議:

  • 使用Async/Await,而不是直接使用Promise
  • 使用babel-preset-env,避免Babel不必要地轉換Async/Await

儘管V8引擎還沒有實現這些優化,請遵循這些建議。當我們為V8實現這些優化的時候,你的程式可以保證最佳的效能。(作者是V8引擎的開發者)

關於Fundebug

Fundebug專注於JavaScript、微信小程式、微信小遊戲,Node.js和Java實時BUG監控。
自從2016年雙十一正式上線,Fundebug累計處理了5億+錯誤事件,得到了眾多知名使用者的認可。歡迎免費試用!