javascript:為什麼Promises比setTimeout()更快?

語言: CN / TW / HK

1.實驗

我們來做個實驗。什麼執行得更快:立即解決的承諾或立即超時(又稱超時0 millisecond)?

Promise.resolve(1).then(function resolve() { console.log('Resolved!'); }); setTimeout(function timeout() { console.log('Timed out!'); }, 0); // logs 'Resolved!' // logs 'Timed out!' Promise.resolve(1)是一個靜態函式,它返回一個立即解決的承諾。setTimeout(callback, 0) ,執行回撥,延遲時間為0 millisecond。

開啟演示並檢查控制檯。你會注意到'Resolved!' 先被記錄下來,然後是'Timeout completed!' 。一個立即解決的承諾比一個立即超時的承諾處理得更快。

可能是因為Promise.resolve(true).then(...) 是在setTimeout(..., 0) 之前呼叫的,所以承諾的處理速度更快? 這個問題很合理。

讓我們稍微改變一下實驗的條件,先呼叫setTimeout(..., 0)

setTimeout(function timeout() { console.log('Timed out!'); }, 0); Promise.resolve(1).then(function resolve() { console.log('Resolved!'); }); // logs 'Resolved!' // logs 'Timed out!' 開啟演示,看看控制檯。嗯......同樣的結果!

實驗表明,一個立即解決的承諾會在立即超時之前被處理。最大的問題是...為什麼?

2.事件迴圈

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

注意:如果你對事件迴圈不熟悉,我建議在進一步閱讀之前先看這個視訊

Event Loop Empty

呼叫堆疊是一個LIFO(Last In, First Out)結構,用於儲存程式碼執行過程中建立的執行環境。簡單地說,呼叫堆疊執行函式。

網路API是非同步操作(獲取請求、承諾、定時器)及其回撥的地方,等待完成。

任務佇列(也叫巨集任務)是一個FIFO(先進先出)結構,它儲存著準備執行的非同步操作的回撥。例如,一個超時的setTimeout() 的回撥--準備被執行--被排在任務佇列中。

任務佇列(也被稱為微任務)是一個FIFO(先進先出)結構,儲存著準備執行的承諾的回撥。例如,一個已履行的承諾的解析或拒絕回撥被排在作業佇列中。

最後,事件迴圈永久地監視呼叫棧是否為空。如果呼叫棧是空的,事件迴圈會檢視作業佇列或任務佇列,並將任何準備執行的回撥排入呼叫棧。

3.工作佇列與任務佇列

讓我們再從事件迴圈的角度看一下這個實驗。我將對程式碼的執行做一個逐步的分析。

A) 呼叫堆疊執行setTimeout(..., 0) ,並安排一個定時器。timeout() 回撥被儲存在Web APIs中。

setTimeout(function timeout() { console.log('Timed out!'); }, 0); Promise.resolve(1).then(function resolve() { console.log('Resolved!'); });

Event Loop

B) 呼叫堆疊執行Promise.resolve(true).then(resolve) ,並安排了一個承諾解析。resolved() 回撥儲存在Web APIs中。 setTimeout(function timeout() { console.log('Timed out!'); }, 0); Promise.resolve(1).then(function resolve() { console.log('Resolved!'); });

Event Loop

C)承諾立即被解決,同時計時器也立即超時。因此,定時器回撥timeout()排到任務佇列中,承諾回撥resolve()排到工作佇列中。

Event Loop

D) 現在是有趣的部分:事件迴圈優先於任務去排隊工作。事件迴圈從作業佇列中取消承諾回撥resolve() ,並把它放到呼叫棧中。然後呼叫堆疊執行承諾回撥resolve()setTimeout(function timeout() { console.log('Timed out!'); }, 0); Promise.resolve(1).then(function resolve() { console.log('Resolved!'); }); Event Loop

E) 最後,事件迴圈從任務佇列中的定時器回撥timeout() ,並放入呼叫堆疊。然後呼叫堆疊執行定時器回撥timeout()

setTimeout(function timeout() { console.log('Timed out!'); }, 0); Promise.resolve(1).then(function resolve() { console.log('Resolved!'); });

'Timed out!' 被記錄到控制檯。

Event Loop

呼叫堆疊是空的。指令碼的執行已經完成。

4.總結

為什麼一個立即解決的承諾比一個立即的定時器處理得快?

因為事件迴圈優先從作業佇列(儲存已實現的承諾的回撥)中去排隊作業,而不是從任務佇列(儲存已超時的setTimeout() 回撥)中去排隊任務。