宏任務(macrotask )和微任務(microtask )
宏任務和微任務都是我們在開發工作中經常用到的。
宏任務(macrotask )和微任務(microtask )
macrotask 和 microtask 表示異步任務的兩種分類。
在掛起任務時,JS 引擎
會將所有任務按照類別分到這兩個隊列中,首先在 macrotask
的隊列(這個隊列也被叫做 task queue
)中取出第一個任務,執行完畢后取出 microtask
隊列中的所有任務順序執行;之后再取 macrotask
任務,周而復始,直至兩個隊列的任務都取完。
“先查看是否有事件可執行”,如果有這優先級限制,那應該可以這樣理解:
其實并不止一個消息隊列,有異步隊列
和事件隊列
,而事件隊列總是優先于異步隊列被空閑下來的JS線程取用
宏任務一般是:包括整體代碼script
,setTimeout
,setInterval
、I/O
、UI render
。
微任務主要是:Promise
、Object.observe
、MutationObserver
。
-
宏任務和微任務之間的關系
宏任務和微任務之間的關系
區別體現demo
- 1.Promise在前,setTimeout在后
new Promise((resolve) => {
console.log('外層宏事件2');
resolve()
}).then(() => {
console.log('微事件1');
}).then(()=>{
console.log('微事件2')
})
console.log('外層宏事件1');
setTimeout(() => {
//執行后 回調一個宏事件
console.log('內層宏事件3')
}, 0)
結果:
外層宏事件2
外層宏事件1
微事件1
微事件2
內層宏事件3
- 2.setTimeout在前,Promise在后
setTimeout(() => {
//執行后 回調一個宏事件
console.log('內層宏事件3')
}, 0)
console.log('外層宏事件1');
new Promise((resolve) => {
console.log('外層宏事件2');
resolve()
}).then(() => {
console.log('微事件1');
}).then(()=>{
console.log('微事件2')
})
結果:
外層宏事件1
外層宏事件2
微事件1
微事件2
內層宏事件3
NodeJS的宏任務和微任務跟瀏覽器環境又有什么區別
瀏覽器的Event loop是在HTML5中定義的規范,而node中則由libuv庫實現。libuv庫流程大體分為6個階段:timers,I/O callbacks,idle、prepare,poll,check,close callbacks,和瀏覽器的microtask,macrotask那一套有區別。
兩者執行的規則不同,所以不相同。
// js event loop (指主線程從“任務隊列”中循環讀取任務)
//
// event queue 可能同時不止一個
//
// 宏任務(macrotask)event queue (macro-task(宏任務):script(主程序代碼),,setTimeout,setInterval, I/O, setImmediate(node環境),UI rendering)
// ^
// | 異步
// 主線程->
// | | 異步
// | V
// | 微任務(microtask) event queue (micro-task(微任務):Promise,process.nextTick(node環境),Object.observe, MutationObserver)
// |
// V
// 如果是瀏覽器環境 click等事件隊列總是優先于異步隊列被空閑下來的JS線程取用
// 執行順序 script(主程序代碼)—>process.nextTick—>Promises…——>setTimeout——>setInterval——>setImmediate——> I/O——>UI rendering
// nextTick 優先級比 Promise 等 microTask 高,setTimeout和setInterval優先級比setImmediate高
瀏覽器環境
瀏覽器環境下的 異步任務 分為 宏任務(macroTask) 和 微任務(microTask):
宏任務(macroTask):script
中代碼、setTimeout
、setInterval
、I/O
、UI render
;
微任務(microTask): Promise
、Object.observe
、MutationObserver
。
當滿足執行條件時,宏任務
(macroTask) 和 微任務
(microTask) 會各自被放入對應的隊列:宏隊列
(Macrotask Queue) 和 微隊列
(Microtask Queue) 中等待執行。
Node 環境
在 Node 環境中 任務類型 相對就比瀏覽器環境下要復雜一些:
microTask
:微任務;
nextTick
:process.nextTick;
timers
:執行滿足條件的 setTimeout 、setInterval 回調;
I/O callbacks
:是否有已完成的 I/O 操作的回調函數,來自上一輪的 poll 殘留;
poll
:等待還沒完成的 I/O 事件,會因 timers 和超時時間等結束等待;
check
:執行 setImmediate 的回調;
close callbacks
:關閉所有的 closing handles ,一些 onclose 事件;
idle/prepare 等等:可忽略。
因此,也就產生了執行事件循環相應的任務隊列 Timers Queue、I/O Queue、Check Queue 和 Close Queue。
2.執行過程
瀏覽器環境
先執行<script>
中的同步任務,然后所有微任務,一個宏任務,所有微任務,一個宏任務......
執行完主執行線程中的任務;
取出 Microtask Queue 中任務執行直到清空;
取出 Macrotask Queue 中一個任務執行;
重復 2 和 3 。
需要 注意 的是:
在瀏覽器頁面中可以認為初始執行線程中沒有代碼,每一個<script>
中的代碼是一個獨立的 task ,即會執行完前面的<script>
中創建的 microTask
再執行后面的<script>
中的同步代碼;
如果 microTask
一直被添加,則會繼續執行 microTask ,“卡死” macroTask;
部分版本瀏覽器有執行順序與上述不符的情況,可能是不符合標準或 js 與 html 部分標準沖突;
Promise 的then
和catch
才是 microTask ,本身的內部代碼不是;
個別瀏覽器獨有API未列出。
Node 環境
循環之前
在進入第一次循環之前,會先進行如下操作:
同步任務;
發出異步請求;
規劃定時器生效的時間;
執行process.nextTick()
。
開始循環
循環中進行的操作:
清空當前循環內的 Timers Queue
,清空 NextTick Queue
,清空 Microtask Queue
;
清空當前循環內的 I/O Queue
,清空NextTick Queue
,清空 Microtask Queue
;
清空當前循環內的 Check Queue
,清空 NextTick Queue
,清空 Microtask Queue
;
清空當前循環內的 Close Queue
,清空 NextTick Queue
,清空Microtask Queue
;
進入下輪循環。
可以看出,nextTick
優先級比 Promise
等 microTask
高,setTimeout
和setInterval
優先級比setImmediate
高。
注意
在整個過程中,需要 注意 的是:
如果在 timers 階段執行時創建了setImmediate 則會在此輪循環的 check 階段執行,如果在 timers 階段創建了setTimeout,由于 timers 已取出完畢,則會進入下輪循環,check 階段創建 timers 任務同理;
setTimeout優先級比setImmediate高,但是由于setTimeout(fn,0)的真正延遲不可能完全為 0 秒,可能出現先創建的setTimeout(fn,0)而比setImmediate的回調后執行的情況。