面試官:為什么 Promise 比setTimeout() 快?
本文已經過原作者 devinduct 授權翻譯。
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)是一個靜態函數,它返回一個立即解析的promise。setTimeout(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 工作方式的主要組成部分。
調用堆棧是一個LIFO(后進先出)結構,它存儲在代碼執行期間創建的執行上下文。簡單地說,調用堆棧執行這些函數。
Web api是異步操作(fetch 請求、promise、計時器)及其回調等待完成的地方。
**task queue (任務隊列)是一個FIFO(先進先出)**結構,它保存準備執行的異步操作的回調。例如,超時的setTimeout()的回調函數或準備執行的單擊按鈕事件處理程序都在任務隊列中排隊。
**job queue (作業隊列)**是一個FIFO(先入先出)結構,它保存準備執行的promise 的回調。例如,已完成的承諾的resolve或reject回調被排在作業隊列中。
最后,事件循環永久監聽調用堆棧是否為空。如果調用堆棧為空,則事件循環查看作業隊列或任務隊列,并將準備執行的任何回調分派到調用堆棧中。
3.作業隊列與任務隊列
我們從事件循環的角度來看這個實驗,我將對代碼執行進行一步一步的分析。
A)調用堆棧執行setTimeout(..., 0)并計劃一個計時器, timeout()回調存儲在Web API中:
B)調用堆棧執行 Promise.resolve(true).then(resolve)并安排一個 promise 解決方案。resolved()回調存儲在Web API中:
C)promise 立即被解析,同時計時器也立即執行。這樣,定時器回調timeout()進入任務隊列,promise回調resolve()進入作業隊列
D)現在是有趣的部分:作業隊列(微任務)優先級高于任務隊列(宏任務)。事件循環從作業隊列中取出promise回調resolve()并將其放入調用堆棧中。然后,調用堆棧執行promise回調resolve():
E)最后,事件循環將計時器回調timeout()從任務隊列中出隊到調用堆棧中。然后,調用堆棧執行計時器回調timeout():
調用堆棧為空,已完成腳本的執行。
總結
為什么立即解決的 promise 比立即執行定時器處理得更快?
由于事件循環優先級的存在,因此與任務隊列(存儲超時的setTimeout()回調)相比,作業隊列(用于存儲已實現的Promise回調)的優先級更高。
完~ 我是小智,我要去刷碗了,我們下期見!
作者:Milos Protic 譯者:前端小智 來源:devinduct原文:https://dmitripavlutin.com/javascript-promises-settimeout/
本文轉載自微信公眾號「 大遷世界」,可以通過以下二維碼關注。轉載本文請聯系 大遷世界公眾號。