【譯】Async/await和Promise的不為人知的祕密

NO IMAGE

大家知道,Async/await是generator和Promise的語法糖,但僅僅是語法糖嗎? 它們兩個的性能有沒有區別呢, 又或者 promise.then()和await 同為微任務,但是它們的執行順序是怎樣的呢?

首先先介紹Async/await是如何優化JavaScript引擎的堆棧處理

是的,你沒有看錯,通過標題你就知道了,async/await相比較Promise來說,是優化了堆棧處理的,也就是說,在這一點上,Async/await是比Promise性能好的

與直接使用Promise相比,使用Async/Await不僅可以提高代碼的可讀性,同時也可以優化JavaScript引擎的執行方式

說完 ‘是什麼’,接下來我們討論一下 ‘為什麼’

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

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

Promise.then()

觀察下面代碼, 假設b()返回一個promise

const a = () => {
b().then(() => c())
}

當調用a()函數時,這些事情同步發生,b()函數產生一個promise對象,調用then方法,Promise會在將來的某個時刻resolve,也就是把then裡的回調函數添加到回調鏈。(如果這一塊不太明白,可以仔細學習promise,或者讀一讀promise源碼並嘗試寫一寫,相信你更通透),這樣,a()函數就執行完了,在這個過程中,a()函數並不會暫停,因此在異步函數resolve的時候,a()的作用域已經不存在了,那要如何生成包含a()的堆棧信息呢? 為了解決這個問題,JavaScripts引擎要做一些額外的工作;它會及時記錄並保存堆棧信息。對於V8引擎來說,這些堆棧信息隨著Promise在Promise鏈中傳遞,這樣c()函數在需要的時候也能獲取堆棧信息。但是這無疑造成了額外的開銷,會降低性能;保存堆棧信息會佔用額外的內存。

Await

我們可以用Async/await來實現一下

const a = () => {
await b()
c()
}

使用await的時候,無需存儲堆棧信息,因為存儲b()到a()的指針的足夠了。當b()函數執行的時候,a()函數被暫停了,因此a()函數的作用域還在內存可以訪問。如果b()拋出一個錯誤,堆棧通過指針迅速生成。如果c()函數拋出一個錯誤,堆棧信息也可以像同步函數一樣生成,因為c()是在a()中執行的。不論是b()還是c(),我們都不需要去存儲堆棧信息,因為堆棧信息可以在需要的時候立即生成。而存儲指針,顯然比存儲堆棧更加節省內存

結論

很多ECMAScript語法特性看起來都只是些語法糖,其實並非如此,至少Async/await絕不僅僅是語法糖
為了讓JavaScript引擎處理堆棧的方式性能更高,請儘量使用Async/await,而不是直接使用Promise

tip: 文章開頭所說的執行順序,請移步查看下一篇文章~

相關文章

字節跳動前端校招一二三面+hr面

快手前端校招一二面

京東數字科技實習前端一面+hr面

Boss直聘實習前端(可轉正)一面