溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Node事件循環(huán)機(jī)制是什么

發(fā)布時(shí)間:2023-03-17 09:37:41 來(lái)源:億速云 閱讀:127 作者:iii 欄目:web開(kāi)發(fā)

這篇文章主要介紹“Node事件循環(huán)機(jī)制是什么”,在日常操作中,相信很多人在Node事件循環(huán)機(jī)制是什么問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Node事件循環(huán)機(jī)制是什么”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

雖然js可以在瀏覽器中執(zhí)行又可以在node中執(zhí)行,但是它們的事件循環(huán)機(jī)制并不是一樣的。并且有很大的區(qū)別。

EventLoop機(jī)制概述

在說(shuō)Node事件循環(huán)機(jī)制之前,我們先來(lái)討論兩個(gè)問(wèn)題

為什么要學(xué)習(xí)事件循環(huán)機(jī)制?

學(xué)習(xí)事件循環(huán)可以讓開(kāi)發(fā)者明白JavaScript的運(yùn)行機(jī)制是怎么樣的。

事件循環(huán)機(jī)制做的是什么事情?

事件循環(huán)機(jī)制用于管理異步API的回調(diào)函數(shù)什么時(shí)候回到主線程中執(zhí)行

Node.js采用的是異步IO模型。同步API在主線程中執(zhí)行,異步API在底層的C++維護(hù)的線程中執(zhí)行,異步API的回調(diào)函數(shù)也會(huì)在主線程中執(zhí)行。【相關(guān)教程推薦:nodejs視頻教程、編程教學(xué)】

在Javascript應(yīng)用運(yùn)行時(shí),眾多異步API的回調(diào)函數(shù)什么時(shí)候能回到主線程中調(diào)用呢?這就是事件環(huán)環(huán)機(jī)制做的事情,管理異步API的回調(diào)函數(shù)什么時(shí)候回到主線程中執(zhí)行。

EventLoop的六個(gè)階段

Node中的事件循環(huán)分為六個(gè)階段。

Node事件循環(huán)機(jī)制是什么

在事件循環(huán)中的每個(gè)階段都有一個(gè)隊(duì)列,存儲(chǔ)要執(zhí)行的回調(diào)函數(shù),事件循環(huán)機(jī)制會(huì)按照先進(jìn)先出的方式執(zhí)行他們直到隊(duì)列為空。

這六個(gè)階段都存儲(chǔ)著異步回調(diào)函數(shù),所以還是遵循先執(zhí)行主線程同步代碼,當(dāng)同步代碼執(zhí)行完后再來(lái)輪詢(xún)這六個(gè)階段。

接下來(lái),我們來(lái)詳細(xì)看看這六個(gè)階段里面存儲(chǔ)的都是什么

Timers

Timers:用于存儲(chǔ)定時(shí)器的回調(diào)函數(shù)(setlnterval,setTimeout)。

Pendingcallbacks

Pendingcallbacks:執(zhí)行與操作系統(tǒng)相關(guān)的回調(diào)函數(shù),比如啟動(dòng)服務(wù)器端應(yīng)用時(shí)監(jiān)聽(tīng)端口操作的回調(diào)函數(shù)就在這里調(diào)用。

idle,prepare

idle,prepare:系統(tǒng)內(nèi)部使用。(這個(gè)我們程序員不用管)

Poll

Poll:存儲(chǔ)1/O操作的回調(diào)函數(shù)隊(duì)列,比如文件讀寫(xiě)操作的回調(diào)函數(shù)。

在這個(gè)階段需要特別注意,如果事件隊(duì)列中有回調(diào)函數(shù),則執(zhí)行它們直到清空隊(duì)列 ,否則事件循環(huán)將在此階段停留一段時(shí)間以等待新的回調(diào)函數(shù)進(jìn)入。

但是對(duì)于這個(gè)等待并不是一定的,而是取決于以下兩個(gè)條件:

  • 如果setlmmediate隊(duì)列(check階段)中存在要執(zhí)行的調(diào)函數(shù)。這種情況就不會(huì)等待。

  • timers隊(duì)列中存在要執(zhí)行的回調(diào)函數(shù),在這種情況下也不會(huì)等待。事件循環(huán)將移至check階段,然后移至Closingcallbacks階段,并最終從timers階段進(jìn)入下一次循環(huán)。

Check

Check:存儲(chǔ)setlmmediate的回調(diào)函數(shù)。

Closingcallbacks

Closingcallbacks:執(zhí)行與關(guān)閉事件相關(guān)的回調(diào),例如關(guān)閉數(shù)據(jù)庫(kù)連接的回調(diào)函數(shù)等。

宏任務(wù)與微任務(wù)

跟瀏覽器中的js一樣,node中的異步代碼也分為宏任務(wù)和微任務(wù),只是它們之間的執(zhí)行順序有所區(qū)別。

我們?cè)賮?lái)看看Node中都有哪些宏任務(wù)和微任務(wù)

宏任務(wù)

  • setlnterval

  • setimeout

  • setlmmediate

  • I/O

微任務(wù)

  • Promise.then

  • Promise.catch

  • Promise.finally

  • process.nextTick

node中,對(duì)于微任務(wù)和宏任務(wù)的執(zhí)行順序到底是怎樣的呢?

微任務(wù)和宏任務(wù)的執(zhí)行順序

node中,微任務(wù)的回調(diào)函數(shù)被放置在微任務(wù)隊(duì)列中,宏任務(wù)的回調(diào)函數(shù)被放置在宏任務(wù)隊(duì)列中。

微任務(wù)優(yōu)先級(jí)高于宏任務(wù)。當(dāng)微任務(wù)事件隊(duì)列中存在可以執(zhí)行的回調(diào)函數(shù)時(shí),事件循環(huán)在執(zhí)行完當(dāng)前階段的回調(diào)函數(shù)后會(huì)暫停進(jìn)入事件循環(huán)的下一個(gè)階段,而會(huì)立即進(jìn)入微任務(wù)的事件隊(duì)列中開(kāi)始執(zhí)行回調(diào)函數(shù),當(dāng)微任務(wù)隊(duì)列中的回調(diào)函數(shù)執(zhí)行完成后,事件循環(huán)才會(huì)進(jìn)入到下一個(gè)段開(kāi)始執(zhí)行回調(diào)函數(shù)。

對(duì)于微任務(wù)我們還有個(gè)點(diǎn)需要特別注意。那就是雖然nextTick同屬于微任務(wù),但是它的優(yōu)先級(jí)是高于其它微任務(wù),在執(zhí)行微任務(wù)時(shí),只有nextlick中的所有回調(diào)函數(shù)執(zhí)行完成后才會(huì)開(kāi)始執(zhí)行其它微任務(wù)。

總的來(lái)說(shuō)就是當(dāng)主線程同步代碼執(zhí)行完畢后會(huì)優(yōu)先清空微任務(wù)(如果微任務(wù)繼續(xù)產(chǎn)生微任務(wù)則會(huì)再次清空),然后再到下個(gè)事件循環(huán)階段。并且微任務(wù)的執(zhí)行是穿插在事件循環(huán)六個(gè)階段中間的,也就是每次事件循環(huán)進(jìn)入下個(gè)階段前會(huì)判斷微任務(wù)隊(duì)列是否為空,為空才會(huì)進(jìn)入下個(gè)階段,否則先清空微任務(wù)隊(duì)列。

下面我們用代碼實(shí)操來(lái)驗(yàn)證前面所說(shuō)的。

代碼實(shí)例

先執(zhí)行同步再執(zhí)行異步

Node應(yīng)用程序啟動(dòng)后,并不會(huì)立即進(jìn)入事件循環(huán),而是先執(zhí)行同步代碼,從上到下開(kāi)始執(zhí)行,同步API立即執(zhí)行,異步API交給C++維護(hù)的線程執(zhí)行,異步API的回調(diào)函數(shù)被注冊(cè)到對(duì)應(yīng)的事件隊(duì)列中。當(dāng)所有同步代碼執(zhí)行完成后,才會(huì)進(jìn)入事件循環(huán)。

console.log("start");

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

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

console.log("end");

我們來(lái)看執(zhí)行結(jié)果

Node事件循環(huán)機(jī)制是什么

可以看到,先執(zhí)行同步代碼,然后才會(huì)進(jìn)入事件循環(huán)執(zhí)行異步代碼,在timers階段執(zhí)行兩個(gè)setTimeout回調(diào)。

setTimeout一定會(huì)先于setImmediate執(zhí)行嗎

我們知道setTimeout是在timers階段執(zhí)行,setImmediate是在check階段執(zhí)行。并且事件循環(huán)是從timers階段開(kāi)始的。所以會(huì)先執(zhí)行setTimeout再執(zhí)行setImmediate。

對(duì)于上面的分析一定對(duì)嗎?

我們來(lái)看例子

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");

執(zhí)行上面的代碼,輸出如下

Node事件循環(huán)機(jī)制是什么

先執(zhí)行setTimeout再執(zhí)行setImmediate

接下來(lái)我們來(lái)改造下上面的代碼,把延遲器去掉,看看會(huì)輸出什么

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

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

我們運(yùn)行了七次,可以看到其中有兩次是先運(yùn)行的setImmediate

Node事件循環(huán)機(jī)制是什么

怎么回事呢?不是先timers階段再到check階段嗎?怎么會(huì)變呢?

其實(shí)這就得看進(jìn)入事件循環(huán)的時(shí)候,異步回調(diào)有沒(méi)有完全準(zhǔn)備好了。對(duì)于最開(kāi)始的例子,因?yàn)橛?000毫秒的延遲,所以進(jìn)入事件循環(huán)的時(shí)候,setTimeout回調(diào)是一定準(zhǔn)備好了的。所以執(zhí)行順序不會(huì)變。但是對(duì)于這個(gè)例子,因?yàn)橹骶€程沒(méi)有同步代碼需要執(zhí)行,所以一開(kāi)始就進(jìn)入事件循環(huán),但是在進(jìn)入事件循環(huán)的時(shí)候,setTimeout的回調(diào)并不是一定完全準(zhǔn)備好的,所以就會(huì)有先到check階段執(zhí)行setImmediate回調(diào)函數(shù),再到下一次事件循環(huán)的timers階段來(lái)執(zhí)行setTimeout的回調(diào)。

那在什么情況下同樣的延遲時(shí)間,setImmediate回調(diào)函數(shù)一定會(huì)優(yōu)先于setTimeout的回調(diào)呢?

其實(shí)很簡(jiǎn)單,只要將這兩者放到timers階段和check階段之間的Pendingcallbacks、idle,prepare、poll階段中任意一個(gè)階段就可以了。因?yàn)檫@些階段完執(zhí)行完是一定會(huì)先到check再到timers階段的。

我們以poll階段為例,將這兩者寫(xiě)在IO操作中。

const fs = require("fs");

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

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

我們也來(lái)執(zhí)行七次,可以看到,每次都是setImmediate先執(zhí)行。

Node事件循環(huán)機(jī)制是什么

所以總的來(lái)說(shuō),同樣的延遲時(shí)間,setTimeout并不是百分百先于setImmediate執(zhí)行。

先微任務(wù)再宏任務(wù)

主線程同步代碼執(zhí)行完畢后,會(huì)先執(zhí)行微任務(wù)再執(zhí)行宏任務(wù)。

我們來(lái)看下面的例子

console.log("start");

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

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

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

console.log("end");

我們運(yùn)行一下看結(jié)果,可以看到它是先執(zhí)行了微任務(wù)然后再執(zhí)行宏任務(wù)

Node事件循環(huán)機(jī)制是什么

nextTick優(yōu)于其它微任務(wù)

在微任務(wù)中nextTick的優(yōu)先級(jí)是最高的。

我們來(lái)看下面的例子

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");

我們運(yùn)行上面的代碼,可以看到就算nextTick定義在resolve后面,它也是先執(zhí)行的。

Node事件循環(huán)機(jī)制是什么

微任務(wù)穿插在各個(gè)階段間執(zhí)行

怎么理解這個(gè)穿插呢?其實(shí)就是在事件循環(huán)的六個(gè)階段每個(gè)階段執(zhí)行完后會(huì)清空微任務(wù)隊(duì)列。

我們來(lái)看例子,我們建立了timers、check、poll三個(gè)階段,并且每個(gè)階段都產(chǎn)生了微任務(wù)。

// 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");
  });
});

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

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

我們來(lái)執(zhí)行上面的代碼

Node事件循環(huán)機(jī)制是什么

可以看到,先執(zhí)行微任務(wù),再執(zhí)行宏任務(wù)。先process.nextTick -> Promise.resolve。并且如果微任務(wù)繼續(xù)產(chǎn)生微任務(wù)則會(huì)再次清空,所以就又輸出了nextTick Promise.resolve。

接下來(lái)到timer階段,輸出setTimeout,并且產(chǎn)生了一個(gè)微任務(wù),再進(jìn)入到下個(gè)階段前需要清空微任務(wù)隊(duì)列,所以繼續(xù)輸出setTimeout Promise.resolve。

接下來(lái)到check階段,輸出setImmediate,并且產(chǎn)生了一個(gè)微任務(wù),再進(jìn)入到下個(gè)階段前需要清空微任務(wù)隊(duì)列,所以繼續(xù)輸出setImmediate Promise.resolve。

這也就印證了微任務(wù)會(huì)穿插在各個(gè)階段之間運(yùn)行。

Node事件循環(huán)機(jī)制是什么

到此,關(guān)于“Node事件循環(huán)機(jī)制是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI