真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Node中的事件循環(huán)、process.nextTick()實(shí)例分析

這篇文章主要講解了“Node中的事件循環(huán)、process.nextTick()實(shí)例分析”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Node中的事件循環(huán)、process.nextTick()實(shí)例分析”吧!

創(chuàng)新互聯(lián)是一家集網(wǎng)站建設(shè),阿瓦提企業(yè)網(wǎng)站建設(shè),阿瓦提品牌網(wǎng)站建設(shè),網(wǎng)站定制,阿瓦提網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營(yíng)銷,網(wǎng)絡(luò)優(yōu)化,阿瓦提網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競(jìng)爭(zhēng)力。可充分滿足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶成長(zhǎng)自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。

Node中的事件循環(huán)、process.nextTick()實(shí)例分析

什么是事件循環(huán)

事件循環(huán)是 Node.js 處理非阻塞 I/O 操作的機(jī)制——盡管 JavaScript 是單線程處理的——當(dāng)有可能的時(shí)候,它們會(huì)把操作轉(zhuǎn)移到系統(tǒng)內(nèi)核中去。

既然目前大多數(shù)內(nèi)核都是多線程的,它們可在后臺(tái)處理多種操作。當(dāng)其中的一個(gè)操作完成的時(shí)候,內(nèi)核通知 Node.js 將適合的回調(diào)函數(shù)添加到輪詢隊(duì)列中等待時(shí)機(jī)執(zhí)行。我們?cè)诒疚暮竺鏁?huì)進(jìn)行詳細(xì)介紹。

事件循環(huán)機(jī)制解析

當(dāng) Node.js 啟動(dòng)后,它會(huì)初始化事件循環(huán),處理已提供的輸入腳本(或丟入 REPL,本文不涉及到),它可能會(huì)調(diào)用一些異步的 API、調(diào)度定時(shí)器,或者調(diào)用 process.nextTick(),然后開始處理事件循環(huán)。

下面的圖表展示了事件循環(huán)操作順序的簡(jiǎn)化概覽。

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

注意:每個(gè)框被稱為事件循環(huán)機(jī)制的一個(gè)階段。

每個(gè)階段都有一個(gè) FIFO 隊(duì)列來(lái)執(zhí)行回調(diào)。雖然每個(gè)階段都是特殊的,但通常情況下,當(dāng)事件循環(huán)進(jìn)入給定的階段時(shí),它將執(zhí)行特定于該階段的任何操作,然后執(zhí)行該階段隊(duì)列中的回調(diào),直到隊(duì)列用盡或最大回調(diào)數(shù)已執(zhí)行。當(dāng)該隊(duì)列已用盡或達(dá)到回調(diào)限制,事件循環(huán)將移動(dòng)到下一階段,等等。

由于這些操作中的任何一個(gè)都可能調(diào)度_更多的_操作和由內(nèi)核排列在輪詢階段被處理的新事件, 且在處理輪詢中的事件時(shí),輪詢事件可以排隊(duì)。因此,長(zhǎng)時(shí)間運(yùn)行的回調(diào)可以允許輪詢階段運(yùn)行長(zhǎng)于計(jì)時(shí)器的閾值時(shí)間。有關(guān)詳細(xì)信息,請(qǐng)參閱 計(jì)時(shí)器輪詢部分。

注意:在 Windows 和 Unix/Linux 實(shí)現(xiàn)之間存在細(xì)微的差異,但這對(duì)演示來(lái)說(shuō)并不重要。最重要的部分在這里。實(shí)際上有七或八個(gè)步驟,但我們關(guān)心的是 Node.js 實(shí)際上使用以上的某些步驟。

階段概述

  • 定時(shí)器:本階段執(zhí)行已經(jīng)被 setTimeout()setInterval() 的調(diào)度回調(diào)函數(shù)。

  • 待定回調(diào):執(zhí)行延遲到下一個(gè)循環(huán)迭代的 I/O 回調(diào)。

  • idle, prepare:僅系統(tǒng)內(nèi)部使用。

  • 輪詢:檢索新的 I/O 事件;執(zhí)行與 I/O 相關(guān)的回調(diào)(幾乎所有情況下,除了關(guān)閉的回調(diào)函數(shù),那些由計(jì)時(shí)器和 setImmediate() 調(diào)度的之外),其余情況 node 將在適當(dāng)?shù)臅r(shí)候在此阻塞。

  • 檢測(cè)setImmediate() 回調(diào)函數(shù)在這里執(zhí)行。

  • 關(guān)閉的回調(diào)函數(shù):一些關(guān)閉的回調(diào)函數(shù),如:socket.on('close', ...)。

在每次運(yùn)行的事件循環(huán)之間,Node.js 檢查它是否在等待任何異步 I/O 或計(jì)時(shí)器,如果沒有的話,則完全關(guān)閉。

階段的詳細(xì)概述

定時(shí)器

計(jì)時(shí)器指定可以執(zhí)行所提供回調(diào)的 閾值,而不是用戶希望其執(zhí)行的確切時(shí)間。在指定的一段時(shí)間間隔后, 計(jì)時(shí)器回調(diào)將被盡可能早地運(yùn)行。但是,操作系統(tǒng)調(diào)度或其它正在運(yùn)行的回調(diào)可能會(huì)延遲它們。

注意輪詢階段 控制何時(shí)定時(shí)器執(zhí)行。

例如,假設(shè)您調(diào)度了一個(gè)在 100 毫秒后超時(shí)的定時(shí)器,然后您的腳本開始異步讀取會(huì)耗費(fèi) 95 毫秒的文件:

const fs = require('fs');

function someAsyncOperation(callback) {
  // Assume this takes 95ms to complete
  fs.readFile('/path/to/file', callback);
}

const timeoutScheduled = Date.now();

setTimeout(() => {
  const delay = Date.now() - timeoutScheduled;

  console.log(`${delay}ms have passed since I was scheduled`);
}, 100);

// do someAsyncOperation which takes 95 ms to complete
someAsyncOperation(() => {
  const startCallback = Date.now();

  // do something that will take 10ms...
  while (Date.now() - startCallback < 10) {
    // do nothing
  }
});

當(dāng)事件循環(huán)進(jìn)入 輪詢階段時(shí),它有一個(gè)空隊(duì)列(此時(shí) fs.readFile() 尚未完成),因此它將等待剩下的毫秒數(shù),直到達(dá)到最快的一個(gè)計(jì)時(shí)器閾值為止。當(dāng)它等待 95 毫秒過后時(shí),fs.readFile() 完成讀取文件,它的那個(gè)需要 10 毫秒才能完成的回調(diào),將被添加到 輪詢隊(duì)列中并執(zhí)行。當(dāng)回調(diào)完成時(shí),隊(duì)列中不再有回調(diào),因此事件循環(huán)機(jī)制將查看最快到達(dá)閾值的計(jì)時(shí)器,然后將回到 計(jì)時(shí)器階段,以執(zhí)行定時(shí)器的回調(diào)。在本示例中,您將看到調(diào)度計(jì)時(shí)器到它的回調(diào)被執(zhí)行之間的總延遲將為 105 毫秒。

注意:為了防止 輪詢階段餓死事件循環(huán),libuv(實(shí)現(xiàn) Node.js 事件循環(huán)和平臺(tái)的所有異步行為的 C 函數(shù)庫(kù)),在停止輪詢以獲得更多事件之前,還有一個(gè)硬性最大值(依賴于系統(tǒng))。

掛起的回調(diào)函數(shù)

此階段對(duì)某些系統(tǒng)操作(如 TCP 錯(cuò)誤類型)執(zhí)行回調(diào)。例如,如果 TCP 套接字在嘗試連接時(shí)接收到 ECONNREFUSED,則某些 *nix 的系統(tǒng)希望等待報(bào)告錯(cuò)誤。這將被排隊(duì)以在 掛起的回調(diào)階段執(zhí)行。

輪詢

輪詢階段有兩個(gè)重要的功能:

  • 計(jì)算應(yīng)該阻塞和輪詢 I/O 的時(shí)間。

  • 然后,處理 輪詢隊(duì)列里的事件。

當(dāng)事件循環(huán)進(jìn)入 輪詢階段且_沒有被調(diào)度的計(jì)時(shí)器時(shí)_,將發(fā)生以下兩種情況之一:

  • 如果 輪詢隊(duì)列 不是空的

    ,事件循環(huán)將循環(huán)訪問回調(diào)隊(duì)列并同步執(zhí)行它們,直到隊(duì)列已用盡,或者達(dá)到了與系統(tǒng)相關(guān)的硬性限制。

  • 如果 輪詢隊(duì)列 是空的,還有兩件事發(fā)生:

    • 如果腳本被 setImmediate() 調(diào)度,則事件循環(huán)將結(jié)束 輪詢階段,并繼續(xù) 檢查階段以執(zhí)行那些被調(diào)度的腳本。

    • 如果腳本 未被setImmediate()調(diào)度,則事件循環(huán)將等待回調(diào)被添加到隊(duì)列中,然后立即執(zhí)行。

一旦 輪詢隊(duì)列為空,事件循環(huán)將檢查 _已達(dá)到時(shí)間閾值的計(jì)時(shí)器_。如果一個(gè)或多個(gè)計(jì)時(shí)器已準(zhǔn)備就緒,則事件循環(huán)將繞回計(jì)時(shí)器階段以執(zhí)行這些計(jì)時(shí)器的回調(diào)。

檢查階段

此階段允許人員在輪詢階段完成后立即執(zhí)行回調(diào)。如果輪詢階段變?yōu)榭臻e狀態(tài),并且腳本使用 setImmediate() 后被排列在隊(duì)列中,則事件循環(huán)可能繼續(xù)到 檢查階段而不是等待。

setImmediate() 實(shí)際上是一個(gè)在事件循環(huán)的單獨(dú)階段運(yùn)行的特殊計(jì)時(shí)器。它使用一個(gè) libuv API 來(lái)安排回調(diào)在 輪詢階段完成后執(zhí)行。

通常,在執(zhí)行代碼時(shí),事件循環(huán)最終會(huì)命中輪詢階段,在那等待傳入連接、請(qǐng)求等。但是,如果回調(diào)已使用 setImmediate()調(diào)度過,并且輪詢階段變?yōu)榭臻e狀態(tài),則它將結(jié)束此階段,并繼續(xù)到檢查階段而不是繼續(xù)等待輪詢事件。

關(guān)閉的回調(diào)函數(shù)

如果套接字或處理函數(shù)突然關(guān)閉(例如 socket.destroy()),則'close' 事件將在這個(gè)階段發(fā)出。否則它將通過 process.nextTick() 發(fā)出。

setImmediate() 對(duì)比 setTimeout()

setImmediate()setTimeout() 很類似,但是基于被調(diào)用的時(shí)機(jī),他們也有不同表現(xiàn)。

  • setImmediate() 設(shè)計(jì)為一旦在當(dāng)前 輪詢階段完成, 就執(zhí)行腳本。

  • setTimeout() 在最小閾值(ms 單位)過后運(yùn)行腳本。

執(zhí)行計(jì)時(shí)器的順序?qū)⒏鶕?jù)調(diào)用它們的上下文而異。如果二者都從主模塊內(nèi)調(diào)用,則計(jì)時(shí)器將受進(jìn)程性能的約束(這可能會(huì)受到計(jì)算機(jī)上其他正在運(yùn)行應(yīng)用程序的影響)。

例如,如果運(yùn)行以下不在 I/O 周期(即主模塊)內(nèi)的腳本,則執(zhí)行兩個(gè)計(jì)時(shí)器的順序是非確定性的,因?yàn)樗苓M(jìn)程性能的約束:

// timeout_vs_immediate.js
setTimeout(() => {
  console.log('timeout');
}, 0);

setImmediate(() => {
  console.log('immediate');
});


$ node timeout_vs_immediate.js
timeout
immediate

$ node timeout_vs_immediate.js
immediate
timeout

但是,如果你把這兩個(gè)函數(shù)放入一個(gè) I/O 循環(huán)內(nèi)調(diào)用,setImmediate 總是被優(yōu)先調(diào)用:

// timeout_vs_immediate.js
const fs = require('fs');

fs.readFile(__filename, () => {
  setTimeout(() => {
    console.log('timeout');
  }, 0);
  setImmediate(() => {
    console.log('immediate');
  });
});


$ node timeout_vs_immediate.js
immediate
timeout

$ node timeout_vs_immediate.js
immediate
timeout

使用 setImmediate() 相對(duì)于setTimeout() 的主要優(yōu)勢(shì)是,如果setImmediate()是在 I/O 周期內(nèi)被調(diào)度的,那它將會(huì)在其中任何的定時(shí)器之前執(zhí)行,跟這里存在多少個(gè)定時(shí)器無(wú)關(guān)

process.nextTick()

理解 process.nextTick()

您可能已經(jīng)注意到 process.nextTick() 在圖示中沒有顯示,即使它是異步 API 的一部分。這是因?yàn)?process.nextTick() 從技術(shù)上講不是事件循環(huán)的一部分。相反,它都將在當(dāng)前操作完成后處理 nextTickQueue, 而不管事件循環(huán)的當(dāng)前階段如何。這里的一個(gè)_操作_被視作為一個(gè)從底層 C/C++ 處理器開始過渡,并且處理需要執(zhí)行的 JavaScript 代碼。

回顧我們的圖示,任何時(shí)候在給定的階段中調(diào)用 process.nextTick(),所有傳遞到 process.nextTick() 的回調(diào)將在事件循環(huán)繼續(xù)之前解析。這可能會(huì)造成一些糟糕的情況,因?yàn)?strong>它允許您通過遞歸 process.nextTick()調(diào)用來(lái)“餓死”您的 I/O,阻止事件循環(huán)到達(dá) 輪詢階段。

為什么會(huì)允許這樣?

為什么這樣的事情會(huì)包含在 Node.js 中?它的一部分是一個(gè)設(shè)計(jì)理念,其中 API 應(yīng)該始終是異步的,即使它不必是。以此代碼段為例:

function apiCall(arg, callback) {
  if (typeof arg !== 'string')
    return process.nextTick(
      callback,
      new TypeError('argument should be string')
    );
}

代碼段進(jìn)行參數(shù)檢查。如果不正確,則會(huì)將錯(cuò)誤傳遞給回調(diào)函數(shù)。最近對(duì) API 進(jìn)行了更新,允許傳遞參數(shù)給 process.nextTick(),這將允許它接受任何在回調(diào)函數(shù)位置之后的參數(shù),并將參數(shù)傳遞給回調(diào)函數(shù)作為回調(diào)函數(shù)的參數(shù),這樣您就不必嵌套函數(shù)了。

我們正在做的是將錯(cuò)誤傳回給用戶,但僅在執(zhí)行用戶的其余代碼之后。通過使用process.nextTick(),我們保證 apiCall() 始終在用戶代碼的其余部分_之后_和在讓事件循環(huán)繼續(xù)進(jìn)行_之前_,執(zhí)行其回調(diào)函數(shù)。為了實(shí)現(xiàn)這一點(diǎn),JS 調(diào)用棧被允許展開,然后立即執(zhí)行提供的回調(diào),允許進(jìn)行遞歸調(diào)用 process.nextTick(),而不觸碰 RangeError: 超過 V8 的最大調(diào)用堆棧大小 限制。

這種設(shè)計(jì)原理可能會(huì)導(dǎo)致一些潛在的問題。 以此代碼段為例:

let bar;

// this has an asynchronous signature, but calls callback synchronously
function someAsyncApiCall(callback) {
  callback();
}

// the callback is called before `someAsyncApiCall` completes.
someAsyncApiCall(() => {
  // since someAsyncApiCall has completed, bar hasn't been assigned any value
  console.log('bar', bar); // undefined
});

bar = 1;

用戶將 someAsyncApiCall() 定義為具有異步簽名,但實(shí)際上它是同步運(yùn)行的。當(dāng)調(diào)用它時(shí),提供給 someAsyncApiCall() 的回調(diào)是在事件循環(huán)的同一階段內(nèi)被調(diào)用,因?yàn)?someAsyncApiCall() 實(shí)際上并沒有異步執(zhí)行任何事情。結(jié)果,回調(diào)函數(shù)在嘗試引用 bar,但作用域中可能還沒有該變量,因?yàn)槟_本尚未運(yùn)行完成。

通過將回調(diào)置于 process.nextTick() 中,腳本仍具有運(yùn)行完成的能力,允許在調(diào)用回調(diào)之前初始化所有的變量、函數(shù)等。它還具有不讓事件循環(huán)繼續(xù)的優(yōu)點(diǎn),適用于讓事件循環(huán)繼續(xù)之前,警告用戶發(fā)生錯(cuò)誤的情況。下面是上一個(gè)使用 process.nextTick() 的示例:

let bar;

function someAsyncApiCall(callback) {
  process.nextTick(callback);
}

someAsyncApiCall(() => {
  console.log('bar', bar); // 1
});

bar = 1;

這又是另外一個(gè)真實(shí)的例子:

const server = net.createServer(() => {}).listen(8080);

server.on('listening', () => {});

只有傳遞端口時(shí),端口才會(huì)立即被綁定。因此,可以立即調(diào)用 'listening' 回調(diào)。問題是 .on('listening') 的回調(diào)在那個(gè)時(shí)間點(diǎn)尚未被設(shè)置。

為了繞過這個(gè)問題,'listening' 事件被排在 nextTick() 中,以允許腳本運(yùn)行完成。這讓用戶設(shè)置所想設(shè)置的任何事件處理器。

process.nextTick() 對(duì)比 setImmediate()

就用戶而言,我們有兩個(gè)類似的調(diào)用,但它們的名稱令人費(fèi)解。

  • process.nextTick() 在同一個(gè)階段立即執(zhí)行。

  • setImmediate() 在事件循環(huán)的接下來(lái)的迭代或 'tick' 上觸發(fā)。

實(shí)質(zhì)上,這兩個(gè)名稱應(yīng)該交換,因?yàn)?process.nextTick()setImmediate() 觸發(fā)得更快,但這是過去遺留問題,因此不太可能改變。如果貿(mào)然進(jìn)行名稱交換,將破壞 npm 上的大部分軟件包。每天都有更多新的模塊在增加,這意味著我們要多等待每一天,則更多潛在破壞會(huì)發(fā)生。盡管這些名稱使人感到困惑,但它們本身名字不會(huì)改變。

我們建議開發(fā)人員在所有情況下都使用 setImmediate(),因?yàn)樗菀桌斫狻?/p>

為什么要使用 process.nextTick()?

有兩個(gè)主要原因:

  1. 允許用戶處理錯(cuò)誤,清理任何不需要的資源,或者在事件循環(huán)繼續(xù)之前重試請(qǐng)求。

  2. 有時(shí)有讓回調(diào)在棧展開后,但在事件循環(huán)繼續(xù)之前運(yùn)行的必要。

以下是一個(gè)符合用戶預(yù)期的簡(jiǎn)單示例:

const server = net.createServer();
server.on('connection', (conn) => {});

server.listen(8080);
server.on('listening', () => {});

假設(shè) listen() 在事件循環(huán)開始時(shí)運(yùn)行,但 listening 的回調(diào)被放置在 setImmediate() 中。除非傳遞過主機(jī)名,才會(huì)立即綁定到端口。為使事件循環(huán)繼續(xù)進(jìn)行,它必須命中 輪詢階段,這意味著有可能已經(jīng)接收了一個(gè)連接,并在偵聽事件之前觸發(fā)了連接事件。

另一個(gè)示例運(yùn)行的函數(shù)構(gòu)造函數(shù)是從 EventEmitter 繼承的,它想調(diào)用構(gòu)造函數(shù):

const EventEmitter = require('events');
const util = require('util');

function MyEmitter() {
  EventEmitter.call(this);
  this.emit('event');
}
util.inherits(MyEmitter, EventEmitter);

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
  console.log('an event occurred!');
});

你不能立即從構(gòu)造函數(shù)中觸發(fā)事件,因?yàn)槟_本尚未處理到用戶為該事件分配回調(diào)函數(shù)的地方。因此,在構(gòu)造函數(shù)本身中可以使用 process.nextTick() 來(lái)設(shè)置回調(diào),以便在構(gòu)造函數(shù)完成后發(fā)出該事件,這是預(yù)期的結(jié)果:

const EventEmitter = require('events');
const util = require('util');

function MyEmitter() {
  EventEmitter.call(this);

  // use nextTick to emit the event once a handler is assigned
  process.nextTick(() => {
    this.emit('event');
  });
}
util.inherits(MyEmitter, EventEmitter);

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
  console.log('an event occurred!');
});

感謝各位的閱讀,以上就是“Node中的事件循環(huán)、process.nextTick()實(shí)例分析”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)Node中的事件循環(huán)、process.nextTick()實(shí)例分析這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!


網(wǎng)站名稱:Node中的事件循環(huán)、process.nextTick()實(shí)例分析
URL地址:http://weahome.cn/article/jiieoc.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部