關於Nodejs的事件循環Event Loop,網上有各種各樣的介紹,因此本文我們不再針對具體的事件循環進行說明,我們從一個實際的示例來說明Nodejs的事件循環究竟是如何工作。
背景
Nodejs 事件循環是支撐Nodejs 非阻塞IO以及異步執行的基礎,因此理解事件循環的執行也就可以寫出正確的代碼或者說我們就能更快的找出為什么不按我寫的代碼順序執行的原因所在。
說起Nodejs事件循環,不得不提一個圖,介紹的是Nodejs的事件循環示意圖,其中將事件循環分為:timers、pending callback、idle prepare 、poll、check、close。
其中本文我們僅關注三個階段,timers、poll、check。
其中timers階段對應的是setTimeout、setInterval 兩個方法,而這兩個方法對應的也是宏任務。
poll 是異步回調事件,除了setTimeout、setInterval、process.nextTick、Promise、setImmediate之外的事件。
check 階段對應的是serImmediate 事件。
下面我們畫一個表格來說明這三個階段:
階段 | 方法 | 是否宏任務 | 所屬隊列 |
timers | setTimeout、setIntrval | 是 | 宏任務隊列 |
poll | 各種異步回調 | ||
check | setImmediate | 是 | 宏任務隊列 |
另外,process.nextTick 屬於微任務TickQueue隊列,Promise.resolve()屬於others 微任務隊列。
說到這里,有小伙伴會想,說好的事件循環,咋還聊起來宏任務、微任務了,文不對題,差評。
別急,老鐵,之所以會提微任務,是因為微任務也會參與Nodejs的異步執行,因此加上這倆微任務,我們就可以更明確的說明程序代碼的執行順序。
上酸菜,
示例
先簡要說明下結論,微任務執行隊列是按先進先出的順序執行,但微任務隊列之間是有執行順序,nextTick queue的執行優先級大於others 微任務隊列。
另外,微任務會比宏任務先執行。
示例1:如何讓一個同步執行的方法延遲執行?
1 let carName; 2 function myCar() { 3 // 此處需要延遲執行 否則carName會輸出undefined 4 console.log("this is mycar: " + carName); 5 } 6 7 function setCarName() { 8 carName = "Audi"; 9 }
參考做法,將myCar的執行延遲,
1 let carName; 2 function myCar() { 3 // 此處需要延遲執行 否則carName會輸出undefined 4 process.nextTick(()=>{ 5 console.log("this is mycar: " + carName); 6 }); 7 setTimeout(() => { 8 console.log("this is mycar: " + carName); 9 }); 10 Promise.resolve().then(()=>{ 11 console.log("this is mycar: " + carName); 12 }); 13 14 setImmediate(()=>{ 15 console.log("this is mycar: " + carName); 16 }); 17 } 18 19 function setCarName() { 20 carName = "Audi"; 21 } 22 23 myCar(); 24 setCarName();
示例2:宏任務和微任務混合代碼,在Nodejs 事件循環的加持下,會產生怎樣的火花?
先說明幾個原則,
1、在有同步代碼執行的時候,同步代碼的執行優先級會高於任何的異步代碼
2、而異步代碼如果分屬不同的階段,那么執行順序也是固定
3、在同一執行背景下,微任務代碼的優先級會高於宏任務代碼。
簡單示例1:包含單個階段的異步代碼
//同步代碼優先執行 同步代碼的執行優先級最高 console.log("start"); myCar(1); //會加入到事件循環timers階段 setTimeout(() => { console.log("timeout 1"); }); //會加入到事件循環timers階段 setTimeout(() => { console.log("timeout 2"); }); myCar(2); console.log("end"); function myCar(index) { console.log("同步輸出" + index); }
通過該示例,我們會得出一個結論,在同一個事件循環階段,會按照添加順序,先進先出的原則進行執行。
簡單示例2:加入微任務執行
加入process.NextTick及Promise 微任務隊列,請注意我們前面提到,微任務隊列的執行優先級高於宏任務隊列,且nextTickqueue的執行優先級高於Promise所屬的others。
1 //同步代碼優先執行 同步代碼的執行優先級最高 2 console.log("start"); 3 myCar(1); 4 5 //會加入到事件循環timers階段 6 setTimeout(() => { 7 console.log("timeout 1"); 8 }); 9 10 //會加入到事件循環timers階段 11 setTimeout(() => { 12 console.log("timeout 2"); 13 }); 14 15 // 會添加到微任務隊列 nextTickQueue 隊列中 16 process.nextTick(() => { 17 console.log("tick 1"); 18 }); 19 20 //會添加到微任務隊列 others 微任務隊列 21 Promise.resolve().then(() => { 22 console.log("promise 1"); 23 }); 24 25 // 會添加到微任務隊列 nextTickQueue 隊列中 26 process.nextTick(() => { 27 console.log("tick 2"); 28 }); 29 //會添加到微任務隊列 others 微任務隊列 30 Promise.resolve().then(() => { 31 console.log("promise 2"); 32 }); 33 34 myCar(2); 35 console.log("end"); 36 37 function myCar(index) { 38 console.log("同步輸出" + index); 39 }
結論
本文主要描述的是Nodejs 微任務與宏任務執行的先后順序,更多具體的執行細節,請講上述示例手工敲代碼運行之后,仔細回味,針對方法內部包含的也是同理。
1、微任務執行>宏任務任務
2、nextTick >others 執行
3、同步執行代碼執行優先級最高。
4、同一階段或同一隊列的執行,按先進先出的順序執行。
5、代碼的執行和代碼所在的位置無關
根據我最近的學習,總結出一個結論,那就是Nodejs 入門簡單,了解原理很難,加油,少年。