2018-03-23 星期五 農曆 二月初七戊戌年 【狗年】乙卯月 甲寅日
宜: 裁衣、經絡、伐木、開柱眼、拆卸、修造、動土、上樑、合脊、合壽木、入殮、除服、成服、移柩、破土
忌: 祭祀、嫁娶、出行、上樑、掘井
本文采用 自問自答的形式 配合代碼(執行結果)來說解 event loop 的執行機制node
執行環境 : node.js-8.10.0 WebStorm 2017.2.3 bash
console.log(1);
console.log(2);
setTimeout(function(){
console.log(3)
})
setTimeout(function(){
console.log(4);
})
console.log(5)複製代碼
執行:輸入結果 顯示以下異步
如今咱們來實例分析下 這個代碼執行結果爲何是這樣的 : socket
Node是基於單線程的(主要主線程是單線程,用來執行同步任務。等遇到異步任務的時候,會調用另一條異步線程來處理異步任務,如:setTimeout 這些會影響主線程運行的,須要等待一段時間) 函數
代碼執行分析 以下: 主線程開始執行,自上往下,先是開始執行同步任務, 依次執行oop
console.log(1)
console.log(2)
複製代碼
而後遇到 異步任務
ui
setTimeout(function(){
console.log(3)
})
setTimeout(function(){
console.log(4);
})複製代碼
這時候 Node 會把 這些異步任務 push 到一個 異步執行棧 stack 裏面 spa
而後繼續執行 主線程的 任務線程
console.log(5) 複製代碼
等到全部的同步任務任務,這時候 Node 開始執行 異步隊列 棧 stack 裏面的異步任務 code
按照 堆 stack 的特徵 先進後出的特徵 會依次開始執行 棧裏面的異步任務
等待他們執行完畢之後,會生成一個 宏 任務隊列(下面會有詳細的介紹),按照執行先後順序來添加到這個隊列中,
隊列遵循先進先出的規則,開始依次執行 列隊
執行結果以下:
3
4複製代碼
當Node.js啓動時會初始化event loop, 每個event loop都會包含按以下順序六個循環階段,
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘複製代碼
每個階段都有一個裝有callbacks的fifo queue(隊列),當event loop運行到一個指定階段時, node將執行該階段的fifo queue(隊列),當隊列callback執行完或者執行callbacks數量超過該階段的上限時, event loop會轉入下一下階段.
setTimeout(() => {
console.log('setTimeout')
}, 0)
setImmediate(() => {
console.log('setImmediate')
})複製代碼
運行結果:
setImmediate
setTimeout
複製代碼
或者:
setTimeout
setImmediate
複製代碼
爲何結果不肯定呢?
解釋:setTimeout/setInterval 的第二個參數取值範圍是:[1, 2^31 - 1],若是超過這個範圍則會初始化爲 1,即 setTimeout(fn, 0) === setTimeout(fn, 1)。咱們知道 setTimeout 的回調函數在 timer 階段執行,setImmediate 的回調函數在 check 階段執行,event loop 的開始會先檢查 timer 階段,可是在開始以前到 timer 階段會消耗必定時間,因此就會出現兩種狀況:
const fs = require('fs')
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('setTimeout')
}, 0)
setImmediate(() => {
console.log('setImmediate')
})
})
複製代碼
運行結果:
setImmediate
setTimeout
複製代碼
解釋:fs.readFile 的回調函數執行完後:
因此,在 I/O Callbacks 中註冊的 setTimeout 和 setImmediate,永遠都是 setImmediate 先執行。
這兩個概念屬於對異步任務的分類,不一樣的API註冊的異步任務會依次進入自身對應的隊列中,而後等待Event Loop將它們依次壓入執行棧中執行。
task主要包含:setTimeout
、setInterval
、setImmediate
、I/O
、UI交互事件
microtask主要包含:Promise
、process.nextTick
microtask 會優先 task 執行
通常 Event loop 會先清空 microtask 隊列裏面的任務,而後纔回去 執行 task 裏面的 異步任務
console.log(1);
console.log(2);
setImmediate(function(){
console.log(4);
})
setTimeout(function(){
console.log(3)
})
process.nextTick(function(){
console.log('process.nextTick')
})
Promise.resolve().then(function () {
console.log('Promise')
})複製代碼
執行結果以下:
1
2
process.nextTick
Promise
3
4複製代碼
先依次執行同步任務 console.log 輸出
1
2複製代碼
而後開始執行異步任務 stack 裏面的任務 先微任務在宏任務
輸出
process.nextTick
Promise 複製代碼
最後等待微任務執行完畢之後,在執行最後的宏任務
3
4複製代碼
待續。。。。。。