首頁  >  文章  >  web前端  >  深入了解Node事件循環(EventLoop)機制

深入了解Node事件循環(EventLoop)機制

青灯夜游
青灯夜游轉載
2023-03-16 20:11:321834瀏覽

主執行緒從"任務佇列"讀取事件,這個過程是循環不斷的,所以整個的這種運作機制又稱為Event Loop(事件循環)。以下這篇文章就來帶大家掌握Node.js中的eventloop,希望對大家有幫助!

深入了解Node事件循環(EventLoop)機制

雖然js可以在瀏覽器中執行又可以在node中執行,但是它們的事件循環機制並不是一樣的。並且有很大的差別。

EventLoop機制概述

在說Node#事件循環機制之前,我們先討論兩個問題

為什麼要學習事件循環機制?

學習事件循環可以讓開發者明白JavaScript的運作機制是怎麼樣的。

事件循環機製做的是什麼事情?

事件循環機制用於管理非同步API的回呼函數何時回到主執行緒執行

Node.js採用的是非同步IO模型。同步API在主執行緒中執行,非同步API在底層的C 維護的執行緒中執行,非同步API的回呼函數也會在主執行緒中執行。 【相關教學推薦:nodejs影片教學程式設計教學

#在Javascript應用程式執行時,眾多非同步API的回呼函數何時能回到主執行緒中調用呢?這就是事件環環機製做的事情,管理非同步API的回呼函數什麼時候會回到主執行緒執行。

EventLoop的六個階段

Node中的事件循環分為六個階段。

在事件循環中的每個階段都有一個佇列,儲存要執行的回呼函數,事件循環機制會按照先進先出的方式執行他們直到隊列為空。

這六個階段都儲存著非同步回呼函數,所以還是遵循先執行主執行緒同步程式碼,當同步程式碼執行完後再來輪詢這六個階段。

接下來,我們來詳細看看這六個階段裡面儲存的都是什麼

Timers

##Timers :用於儲存定時器的回呼函數(setlnterval,setTimeout)。

Pendingcallbacks

Pendingcallbacks:執行與作業系統相關的回呼函數,例如啟動伺服器端應用程式時監聽連接埠操作的回呼函數就在這裡調用。

idle,prepare

idle,prepare:系統內部使用。 (這個我們程式設計師不用管)

Poll

Poll:儲存1/O操作的回呼函數佇列,例如檔案讀寫操作的回調函數。

在這個階段需要特別注意,如果事件佇列中有回呼函數,則執行它們直到清空佇列 ,否則事件循環將在此階段

停留一段時間以等待新的回呼函數進入。

但是對於這個

等待並不是一定的,而是取決於以下兩個條件:

    #如果setlmmediate佇列(check階段)中存在要執行的調函數。這種情況就不會等待。
  • timers佇列中存在要執行的回呼函數,在這種情況下也不會等待。事件循環將移至check階段,然後移至Closingcallbacks階段,並最終從timers階段進入下一次循環。

Check

Check:儲存setlmmediate的回呼函數。

Closingcallbacks

Closingcallbacks:執行與關閉事件相關的回調,例如關閉資料庫連接的回呼函數等。

巨集任務與微任務

跟瀏覽器中的

js一樣,node中的非同步程式碼也分為巨集任務和微任務,只是它們之間的執行順序有所區別。

我們再來看看

Node中都有哪些巨集任務和微任務

#巨集任務

  • setlnterval

  • setimeout

  • setlmmediate

  • I/O

微任務

###Promise.then############Promise.catch###### ######Promise.finally############process.nextTick############在###node###中,對於微任務和宏任務的執行順序到底是怎麼樣的呢? ###

微任務和巨集任務的執行順序

node#中,微任務的回呼函數被放置在微任務佇列中,巨集任務的回呼函數被放置在巨集任務佇列中。

微任務優先權高於巨集任務。當微任務事件佇列中存在可執行的回呼函數時,事件循環會在執行完目前階段的回呼函數後會暫停進入事件循環的下一個階段,而會立即進入微任務的事件佇列中開始執行回呼函數,當微任務佇列中的回呼函數執行完成後,事件循環才會進入到下一個段開始執行回呼函數。

對於微任務我們還有個點需要特別注意。那就是雖然nextTick同屬於微任務,但是它的優先權是高於其它微任務,在執行微任務時,只有nextlick中的所有回調函數執行完成後才會開始執行其它微任務。

總的來說就是當主執行緒同步程式碼執行完畢後會優先清空微任務(如果微任務繼續產生微任務則會再次清空),然後再到下個事件循環階段。而微任務的執行是穿插在事件循環六個階段中間的,也就是每次事件循環進入下個階段前會判斷微任務隊列是否為空,為空才會進入下個階段,否則先清空微任務隊列。

下面我們用程式碼實操來驗證前面所說的。

程式碼實例

先執行同步再執行非同步

Node應用程式啟動後,並不會立即進入事件循環,而是先執行同步程式碼,從上到下開始執行,同步API立即執行,非同步API交給C 維護的執行緒執行,非同步API的回呼函數被註冊到對應的事件佇列中。當所有同步程式碼執行完成後,才會進入事件循環。

console.log("start");

setTimeout(() => {
  console.log("setTimeout 1");
});

setTimeout(() => {
  console.log("setTimeout 2");
});

console.log("end");

我們來看執行結果

深入了解Node事件循環(EventLoop)機制

可以看到,先執行同步程式碼,然後才會進入事件循環執行非同步程式碼,在 timers階段執行兩個setTimeout回呼。

setTimeout一定會先於setImmediate執行嗎

#我們知道setTimeout是在timers階段執行, setImmediate是在check階段執行。且事件循環是從timers階段開始的。所以會先執行setTimeout再執行setImmediate

對於上面的分析一定對嗎?

我們來看範例

console.log("start");

setTimeout(() => {
  console.log("setTimeout");
});

setImmediate(() => {
  console.log("setImmediate");
});

const sleep = (delay) => {
  const startTime = +new Date();
  while (+new Date() - startTime < delay) {
    continue;
  }
};

sleep(2000);
console.log("end");

執行上面的程式碼,輸出如下

深入了解Node事件循環(EventLoop)機制

#先執行setTimeout再執行setImmediate

接下來我們來改造下上面的程式碼,把延遲器去掉,看看會輸出什麼

setTimeout(() => {
  console.log("setTimeout");
});

setImmediate(() => {
  console.log("setImmediate");
});

我們運行了七次,可以看到其中有兩次是先跑的setImmediate

深入了解Node事件循環(EventLoop)機制

怎麼回事呢?不是先timers階段再到check階段嗎?怎麼會變呢?

其實這就得看進入事件循環的時候,非同步回呼有沒有完全準備好了。對於最開始的例子,因為有2000毫秒的延遲,所以進入事件循環的時候,setTimeout回呼是一定準備好了的。所以執行順序不會變。但對於這個例子,因為主執行緒沒有同步程式碼需要執行,所以一開始就進入事件循環,但是在進入事件循環的時候,setTimeout的回呼並不是一定完全準備好的,所以就會有先到check階段執行setImmediate回呼函數,再到下一次事件循環的timers階段來執行setTimeout的回呼。

那在什麼情況下同樣的延遲時間,setImmediate回呼函數一定會優先於setTimeout的回呼呢?

其實很簡單,只要將這兩者放到timers階段和check階段之間的Pendingcallbacks、idle,prepare、poll階段中任一個階段就可以了。因為這些階段完執行完是一定會先到check再到timers階段的。

我們以poll階段為例,將這兩者寫在IO運算中。

const fs = require("fs");

fs.readFile("./fstest.js", "utf8", (err, data) => {
  setTimeout(() => {
    console.log("setTimeout");
  });

  setImmediate(() => {
    console.log("setImmediate");
  });
});

我們也來執行七次,可以看到,每次都是setImmediate先執行。

深入了解Node事件循環(EventLoop)機制

所以總的來說,同樣的延遲時間,setTimeout並不是百分之百先於setImmediate執行。

先微任务再宏任务

主线程同步代码执行完毕后,会先执行微任务再执行宏任务。

我们来看下面的例子

console.log("start");

setTimeout(() => {
  console.log("setTimeout");
});

setImmediate(() => {
  console.log("setImmediate");
});

Promise.resolve().then(() => {
  console.log("Promise.resolve");
});

console.log("end");

我们运行一下看结果,可以看到它是先执行了微任务然后再执行宏任务

深入了解Node事件循環(EventLoop)機制

nextTick优于其它微任务

在微任务中nextTick的优先级是最高的。

我们来看下面的例子

console.log("start");

setTimeout(() => {
  console.log("setTimeout");
});

setImmediate(() => {
  console.log("setImmediate");
});

Promise.resolve().then(() => {
  console.log("Promise.resolve");
});

process.nextTick(() => {
  console.log("process.nextTick");
});

console.log("end");

我们运行上面的代码,可以看到就算nextTick定义在resolve后面,它也是先执行的。

深入了解Node事件循環(EventLoop)機制

微任务穿插在各个阶段间执行

怎么理解这个穿插呢?其实就是在事件循环的六个阶段每个阶段执行完后会清空微任务队列。

我们来看例子,我们建立了timers、check、poll三个阶段,并且每个阶段都产生了微任务。

// timers阶段
setTimeout(() => {
  console.log("setTimeout");

  Promise.resolve().then(() => {
    console.log("setTimeout Promise.resolve");
  });
});

// check阶段
setImmediate(() => {
  console.log("setImmediate");
  Promise.resolve().then(() => {
    console.log("setImmediate Promise.resolve");
  });
});

// 微任务
Promise.resolve().then(() => {
  console.log("Promise.resolve");
});

// 微任务
process.nextTick(() => {
  console.log("process.nextTick");
  Promise.resolve().then(() => {
    console.log("nextTick Promise.resolve");
  });
});

我们来执行上面的代码

深入了解Node事件循環(EventLoop)機制

可以看到,先执行微任务,再执行宏任务。先process.nextTick -> Promise.resolve。并且如果微任务继续产生微任务则会再次清空,所以就又输出了nextTick Promise.resolve

接下来到timer阶段,输出setTimeout,并且产生了一个微任务,再进入到下个阶段前需要清空微任务队列,所以继续输出setTimeout Promise.resolve

接下来到check阶段,输出setImmediate,并且产生了一个微任务,再进入到下个阶段前需要清空微任务队列,所以继续输出setImmediate Promise.resolve

这也就印证了微任务会穿插在各个阶段之间运行。

深入了解Node事件循環(EventLoop)機制

总结

所以对于Node中的事件循环你只需要背好一以下几点就可以了

  • 当主线程同步代码执行完毕后才会进入事件循环

  • 事件循环总共分六个阶段,并且每个阶段都包括哪些回调需要记清楚。

  • 事件循环中会先执行微任务再执行宏任务。

  • 微任务会穿插在这六个阶段之间执行,每进入到下个阶段前会清空当前的微任务队列。

  • 微任务中process.nextTick的优先级最高,会优先执行。

更多node相关知识,请访问:nodejs 教程

以上是深入了解Node事件循環(EventLoop)機制的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除