本次分享一下從HTML5與PromiseA+規範來迅速理解一波事件循環中的microtask 與macrotask。javascript
歡迎關注個人博客,不按期更新中——html
——什麼時候完結不肯定,寫多少看我會多少!這是已經更新的地址:java
這個系列旨在對一些人們不經常使用遇到的知識點,以及可能經常使用到但未曾深刻了解的部分作一個從新梳理,雖然可能有些部分看起來沒有什麼用,由於平時開發真的用不到!但我的認爲糟粕也好精華也罷裏面所有蘊藏着JS一些偏本質的東西或者說底層規範,若是能適當避開溫馨區來看這些小細節,也許對本身也會有些幫助~文章更新在個人博客,歡迎不按期關注。node
setTimeout(function() {
console.log('setTimeout1');
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
})
}, 0);
setTimeout(function() {
console.log('setTimeout2');
Promise.resolve().then(function() {
console.log('promise3');
}).then(function() {
console.log('promise4');
})
}, 0);複製代碼
從這段代碼中咱們發現裏面有兩個定時器setTimeout
,每一個定時器中還嵌套了Promise。我相信熟悉microtask 與macrotask任務隊列的童鞋能很快的知曉答案,這個東西給個人感受就是清者自清。git
/* 請在新版chrome中打印結果
setTimeout1
promise1
promise2
setTimeout2
promise3
promise4
*/複製代碼
不作解釋直接看下規範中怎麼說的:github
There must be at least one browsing context event loop per user agent, and at most one per unit of related similar-origin browsing contexts. An event loop has one or more task queues.web
一個瀏覽器環境下只能有一個事件循環,同時循環中是能夠存在多個任務隊列的。
同時咱們接着看規範中對event-loop執行過程是如何規定的:chrome
1.Let oldestTask be the oldest task on one of the event loop's task queues.api
2.Set the event loop's currently running task to oldestTask.promise
3.Run oldestTask.
4.Set the event loop's currently running task back to null.
5.Remove oldestTask from its task queue.
6.Microtasks: Perform a microtask checkpoint.
7.Update the rendering
其中的task queues,就是以前提到的macrotask,中文能夠翻譯爲宏任務。顧名思義也就是正常的一些回調執行,好比IO,setTimeout等。簡單來講當事件循環開始後,會將task queues最早進棧的任務執行,以後移出,進行到第六步,作microtask的檢測。發現有microtask的任務那麼會依照以下方式執行:
While the event loop's microtask queue is not empty:
//當microtask隊列中還有任務時,按照下面執行
1.Let oldestMicrotask be the oldest microtask on the event loop's microtask queue.
2.Set the event loop's currently running task to oldestMicrotask.
3.Run oldestMicrotask.
4.Set the event loop's currently running task back to null.
5.Remove oldestMicrotask from the microtask queue.
從這段規範能夠看出,當執行了一個macrotask後會有一個循環來檢查microtask隊列中是否還存在任務,若是有就執行。這說明執行了一個macrotask(宏任務)以後,會執行全部註冊了的microtask(微任務)。
一塊兒看起來很正常對吧?
那麼若是微任務「嵌套」了呢?就像一開始做者給出的那段代碼同樣,promise調用了不少次.then方法。這種狀況文檔中有作出規定麼?有的。
If, while a compound microtask is running, the user agent is required to execute a compound microtask subtask to run a series of steps, the user agent must run the following steps:
1.Let parent be the event loop's currently running task (the currently running compound microtask).
2.Let subtask be a new task that consists of running the given series of steps. The task source of such a microtask is the microtask task source. This is a compound microtask subtask.
3.Set the event loop's currently running task to subtask.
4.Run subtask.
5.Set the event loop's currently running task back to parent.
簡單來講若是有「嵌套」的狀況,註冊的任務都是microtask,那麼就會一股腦得所有執行。
經過上面對文檔的解讀咱們能夠知道如下幾件事:
那麼還剩一件事情就是什麼任務是macrotask,什麼是microtask?
可是!我對於setImmediate與process.nextTick的行爲持懷疑態度。理由最後說!不過在瀏覽器運行環境中咱們不須要關係上面那兩種事件。
在本文一開始就提出,這段代碼要在新版chrome中運行纔會獲得正確結果。那麼不在chrome中呢?
舉個例子,別的做者不一一測試了,這是safari中的結果。咱們能夠看到順序被打亂了。so爲何我執行了同樣的代碼結果卻不一樣?
我的認爲若出現結果不一樣的狀況是因爲不一樣執行環境(chrome, safari, node .etc)將回調須要執行的任務所劃分到的任務隊列與PromiseA+規範中所提到的任務隊列中的任務劃分準則執行不一致致使的。也就是Promise可能被劃分到了macrotask中。有興趣深刻了解的童鞋能夠看下這篇tasks-microtasks-queues-and-schedules.
細心的童鞋可能發現我一直強調的js運行環境是瀏覽器下的事件循環狀況。那麼node中呢?
setTimeout(function() {
console.log('setTimeout1');
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
})
}, 0);
setTimeout(function() {
console.log('setTimeout2');
Promise.resolve().then(function() {
console.log('promise3');
}).then(function() {
console.log('promise4');
})
}, 0);複製代碼
仍是這段代碼,打印出來會不會有區別?多打印幾回結果同樣麼?爲何會這樣?
我只能理解到node經過libuv實現事件循環的方式與規範沒有關係,但具體爲何會打印出不一樣的效果。。求大神@我
慣例po做者的博客,不定時更新中——有問題歡迎在issues下交流,捂臉求star=。=