最近在看《Node.js調試指南》的時候遇到有意思的幾道題,是關於setTimeout, promise.then, process.nextTick, setImmediate的執行順序。今天抽空記錄下這道題的分析過程及背後的原理與知識點。
題目以下:node
// 題目一: setTimeout(()=>{ console.log('setTimeout') },0) setImmediate(()=>{ console.log('setImmediate') }) // 題目二: const promise = Promise.resolve() promise.then(()=>{ console.log('promise') }) process.nextTick(()=>{ console.log('nextTick') }) // 題目三: setTimeout (() => { console.log(1) },0) new Promise((resolve,reject) => { console.log(2) for(let i = 0; i <10000; i++) { i === 9999 && resolve() } console.log(3) }).then(() => { console.log(4) }) console.log(5) // 題目四 setInterval(()=>{ console.log('setInterval') },100) process.nextTick(function tick(){ process.nextTick(tick) })
在分析這幾道題以前先有必要了解下node.js的事件循環編程
咱們能夠簡單理解Event Loop以下:promise
每一個階段都有一個FIFO的回調隊列,當Event Loop執行到這個階段時,就會從當前階段的隊列裏拿出一個任務放到執行棧中執行,在隊列任務清空或者執行的回調數量達到上限後,Event Loop就會進入下一個階段異步
poll階段主要有兩個功能,以下所述:socket
一旦poll queue爲空,則Event Loop將檢查timers,若是有timer的時間到期,則Event Loop將回到timers階段,而後執行timer queue函數
進入 check 階段。oop
進入 closing 階段。線程
檢查是否有活躍的 handles(定時器、IO等事件句柄)。調試
經過上面的事件循環的介紹咱們已經知道setTimeout setImmediate的執行機制,可是並無介紹process.nextTick()和promise.then()。這裏咱們還須要知道宏任務與微任務的概念code
宏任務是指Event Loop在每一個階段執行的任務
宏任務包括 script (總體代碼),setTimeout, setInterval, setImmediate, I/O, UI renderin
微任務是指Event Loop在每一個階段之間執行的任務
微任務包括 process.nextTick, Promise.then,Object.observe,MutationObserver
宏任務與微任務執行順序圖
圖中綠色小塊表示Event Loop的各個階段,執行的是宏任務,粉色箭頭表示執行的是微任務
瞭解到這裏咱們再來分析上面的幾道題
題目一的執行結果是:
setTimeout setImmediate //或者 setImmediate setTimeout
爲何結果不肯定呢?咱們知道setTimeout的回調函數在timer階段執行,setImmediate的回調函數在check階段執行。可是從事件循環開始到timer階段會消耗必定的時間,因此會出現兩種狀況:
題目二的執行結果是
nextTick promise
這裏雖然和process.nextTick同樣,promise.then也將回調函數註冊到microtask,但process.nextTick的microtask queue老是優先於promise的microtask queue執行的
題目三的執行結果是
2 3 5 4 1
Promise構造函數是同步執行的,因此先打印2,3,在打印5,接下來事件循環執行微任務執行promise.then的回調,打印4,而後進入下一個事件循環執行timer階段的回調打印1
題目四的執行結果是
永遠不會打印setInterval
process.nextTick會無限循環,將event loop阻塞在microtask階段,致使event loop上其餘macrotask階段的回調函數沒有機會執行
解決方法一般是用setImmediate代替process.nextTick.
在setImmediate內執行setImmedaite時會將immediate註冊到下一次event loop的check階段,這樣其餘macrotask就有機會執行
至此終於將node.js事件循環宏任務與微任務分析清楚了