本文已經過原作者 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回撥)的優先順序更高。
完~ 我是小智,我要去刷碗了,我們下期見!
原文:https://dmitripavlutin.com/javascript-promises-settimeout/