寫在最前
本次分享一下從HTML5與PromiseA+規(guī)范來迅速理解一波事件循環(huán)中的microtask 與macrotask若厚。
歡迎關注我的博客连锯,不定期更新中——
JavaScript小眾系列開始更新啦
——何時完結不確定,寫多少看我會多少!這是已經更新的地址:
- 小眾系列之終極類型轉換:從hello world看JavaScript隱藏的黑魔法制
- 小眾系列之隱式類型轉換:從[] == ![]看隱式強制轉換機制
- 小眾系列之事件循環(huán):從HTML5與PromiseA+規(guī)范來看事件循環(huán)
這個系列旨在對一些人們不常用遇到的知識點,以及可能常用到但不曾深入了解的部分做一個重新梳理,雖然可能有些部分看起來沒有什么用江兢,因為平時開發(fā)真的用不到!但個人認為糟粕也好精華也罷里面全部蘊藏著JS一些偏本質的東西或者說底層規(guī)范丁频,如果能適當避開舒適區(qū)來看這些小細節(jié)杉允,也許對自己也會有些幫助~文章更新在我的博客,歡迎不定期關注席里。
先來看段代碼
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);
從這段代碼中我們發(fā)現里面有兩個定時器setTimeout
叔磷,每個定時器中還嵌套了Promise。我相信熟悉microtask 與macrotask任務隊列的童鞋能很快的知曉答案奖磁,這個東西給我的感覺就是清者自清改基。
so 結果是什么?
/* 請在新版chrome中打印結果
setTimeout1
promise1
promise2
setTimeout2
promise3
promise4
*/
why?
不做解釋直接看下規(guī)范中怎么說的:
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.
一個瀏覽器環(huán)境下只能有一個事件循環(huán)咖为,同時循環(huán)中是可以存在多個任務隊列的秕狰。
同時我們接著看規(guī)范中對event-loop執(zhí)行過程是如何規(guī)定的:
1.Let oldestTask be the oldest task on one of the event loop's task queues.
2.Set the event loop's currently running task to oldestTask.
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躁染,中文可以翻譯為宏任務鸣哀。顧名思義也就是正常的一些回調執(zhí)行,比如IO吞彤,setTimeout等我衬。簡單來說當事件循環(huán)開始后,會將task queues最先進棧的任務執(zhí)行饰恕,之后移出挠羔,進行到第六步,做microtask的檢測埋嵌。發(fā)現有microtask的任務那么會依照如下方式執(zhí)行:
While the event loop's microtask queue is not empty:
//當microtask隊列中還有任務時褥赊,按照下面執(zhí)行
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.
從這段規(guī)范可以看出,當執(zhí)行了一個macrotask后會有一個循環(huán)來檢查microtask隊列中是否還存在任務莉恼,如果有就執(zhí)行拌喉。這說明執(zhí)行了一個macrotask(宏任務)之后,會執(zhí)行所有注冊了的microtask(微任務)俐银。
一起看起來很正常對吧尿背?
那么如果微任務“嵌套”了呢?就像一開始作者給出的那段代碼一樣捶惜,promise調用了很多次.then方法田藐。這種情況文檔中有做出規(guī)定么?有的吱七。
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,那么就會一股腦得全部執(zhí)行踊餐。
小結
通過上面對文檔的解讀我們可以知道以下幾件事:
- 一個運行環(huán)境有一個事件循環(huán)景醇。PS:有關web worker的概念作者也不太清楚,有興趣的童鞋可以查查
-
重點# 一個事件循環(huán)有多個任務隊列吝岭。目前來看是實現了兩個隊列
- 隊列分為macrotask宏任務隊列與microtask微任務隊列
- 回調的任務會被分配到macrotask與microtask中三痰,具體分配見下文。
- 執(zhí)行一個宏任務窜管,將已經注冊的所有微任務散劫,包括有“嵌套”的全部執(zhí)行。
- 執(zhí)行下一個宏任務幕帆,重復步驟5
那么還剩一件事情就是什么任務是macrotask获搏,什么是microtask?
這張圖來源一篇翻譯PromisA+的文章失乾,里面所提到的關于任務的分類常熙。
但是!我對于setImmediate與process.nextTick的行為持懷疑態(tài)度仗扬。理由最后說症概!不過在瀏覽器運行環(huán)境中我們不需要關系上面那兩種事件。
測試一下代碼
在本文一開始就提出早芭,這段代碼要在新版chrome中運行才會得到正確結果彼城。那么不在chrome中呢?
舉個例子退个,別的作者不一一測試了募壕,這是safari中的結果。我們可以看到順序被打亂了语盈。so為什么我執(zhí)行了一樣的代碼結果卻不同舱馅?
個人認為若出現結果不同的情況是由于不同執(zhí)行環(huán)境(chrome, safari, node .etc)將回調需要執(zhí)行的任務所劃分到的任務隊列與PromiseA+規(guī)范中所提到的任務隊列中的任務劃分準則執(zhí)行不一致導致的。也就是Promise可能被劃分到了macrotask中刀荒。有興趣深入了解的童鞋可以看下這篇tasks-microtasks-queues-and-schedules.
拋一個作者也解釋不清的問題
細心的童鞋可能發(fā)現我一直強調的js運行環(huán)境是瀏覽器下的事件循環(huán)情況代嗤。那么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);
還是這段代碼,打印出來會不會有區(qū)別干毅?多打印幾次結果一樣么宜猜?為什么會這樣?
我只能理解到node通過libuv實現事件循環(huán)的方式與規(guī)范沒有關系硝逢,但具體為什么會打印出不同的效果姨拥。。求大神@我