面試官:為什麼 Promise 比setTimeout() 快?

前端小智發表於2021-12-13
作者:Milos Protic
譯者:前端小智
來源:devinduct
有夢想,有乾貨,微信搜尋 【大遷世界】 關注這個在凌晨還在刷碗的刷碗智。
本文 GitHub https://github.com/qq449245884/xiaozhi 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。

1.實驗

我們來做個實驗。哪個執行得更快:立即解決的 Promise 還是立即setTimeout(也就是0毫秒的setTimeout)?

Promise.resolve(1).then(function resolve() {
  console.log('Resolved!');
});

setTimeout(function timeout() {
  console.log('Timed out!');
}, 0);

// 'Resolved!'
// 'Timed out!'

promise.resolve(1)是一個靜態函式,它返回一個立即解析的promisesetTimeout(callback, 0)0毫秒的延遲執行回撥函式。

我們可以看到先列印'Resolved!',再列印Timeout completed!,立即解決的 promise 比立即setTimeout更快。

是因為Promise.resolve(true).then(...)setTimeout(..., 0)之前被呼叫了,所以 Promise 過程會更快嗎? 公平的問題。

所以,我們稍微更改一下實驗條件,然後先呼叫setTimeout(..., 0)

setTimeout(function timeout() {
  console.log('Timed out!');
}, 0);

Promise.resolve(1).then(function resolve() {
  console.log('Resolved!');
});

// 'Resolved!'
// 'Timed out!'

setTimeout(..., 0)Promise.resolve(true).then(...)之前被呼叫。但,還是先列印Resolved!在列印'Timed out!'

這是為啥呢?

2.事件迴圈

與非同步 JS 相關的問題可以通過研究事件迴圈來回答。我們回顧一下非同步 JS 工作方式的主要組成部分。

clipboard.png

呼叫堆疊是一個LIFO(後進先出)結構,它儲存在程式碼執行期間建立的執行上下文。簡單地說,呼叫堆疊執行這些函式。

Web api是非同步操作(fetch 請求、promise、計時器)及其回撥等待完成的地方。

task queue (任務佇列)是一個FIFO(先進先出)結構,它儲存準備執行的非同步操作的回撥。例如,超時的setTimeout()的回撥函式或準備執行的單擊按鈕事件處理程式都在任務佇列中排隊。

job queue (作業佇列)是一個FIFO(先入先出)結構,它儲存準備執行的promise 的回撥。例如,已完成的承諾的resolvereject回撥被排在作業佇列中。

最後,事件迴圈永久監聽呼叫堆疊是否為空。如果呼叫堆疊為空,則事件迴圈檢視作業佇列或任務佇列,並將準備執行的任何回撥分派到呼叫堆疊中。

3.作業佇列與任務佇列

我們從事件迴圈的角度來看這個實驗,我將對程式碼執行進行一步一步的分析。

A)呼叫堆疊執行setTimeout(..., 0)並計劃一個計時器, timeout()回撥儲存在Web API中:

clipboard.png

clipboard.png

B)呼叫堆疊執行 Promise.resolve(true).then(resolve)並安排一個 promise 解決方案。 resolved()回撥儲存在Web API中:

clipboard.png

clipboard.png

C)promise 立即被解析,同時計時器也立即執行。這樣,定時器回撥timeout()進入任務佇列,promise回撥resolve()進入作業佇列

clipboard.png

D)現在是有趣的部分:作業佇列(微任務)優先順序高於任務佇列(巨集任務)。 事件迴圈從作業佇列中取出promise回撥resolve()並將其放入呼叫堆疊中。 然後,呼叫堆疊執行promise回撥resolve()

clipboard.png

E)最後,事件迴圈將計時器回撥timeout()從任務佇列中出隊到呼叫堆疊中。 然後,呼叫堆疊執行計時器回撥timeout()

clipboard.png

呼叫堆疊為空,已完成指令碼的執行。

總結

為什麼立即解決的 promise 比立即執行定時器處理得更快?

由於事件迴圈優先順序的存在,因此與任務佇列(儲存超時的setTimeout()回撥)相比,作業佇列(用於儲存已實現的Promise回撥)的優先順序更高。

完~ 我是小智,我要去刷碗了,我們下期見!


程式碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug

原文: https://dmitripavlutin.com/ja...

交流

有夢想,有乾貨,微信搜尋 【大遷世界】 關注這個在凌晨還在刷碗的刷碗智。

本文 GitHub https://github.com/qq44924588... 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。

相關文章