欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入淺析Node.js 事件循環(huán)、定時(shí)器和process.nextTick()

 更新時(shí)間:2018年10月22日 09:29:53   作者:Owen''''''''''''''''s Blogs  
這篇文章主要介紹了Node.js 事件循環(huán)、定時(shí)器和process.nextTick()的相關(guān)知識(shí),非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

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

盡管JavaScript是單線程的,但通過盡可能將操作放到系統(tǒng)內(nèi)核執(zhí)行,事件循環(huán)允許Node.js執(zhí)行非阻塞I/O操作。

由于現(xiàn)代大多數(shù)內(nèi)核都是多線程的,因此它們可以處理在后臺(tái)執(zhí)行的多個(gè)操作。 當(dāng)其中一個(gè)操作完成時(shí),內(nèi)核會(huì)告訴Node.js,以便可以將相應(yīng)的回調(diào)添加到 輪詢隊(duì)列 中以最終執(zhí)行。 我們將在本主題后面進(jìn)一步詳細(xì)解釋。

事件循環(huán)解釋

當(dāng)Node.js啟動(dòng)時(shí),它初始化事件循環(huán),處理提供的輸入腳本(或放入 REPL ,本文檔未涉及),這可能會(huì)進(jìn)行異步API調(diào)用,調(diào)度計(jì)時(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)的“階段”。

每個(gè)階段都要執(zhí)行一個(gè)FIFO的回調(diào)隊(duì)列。 雖然每個(gè)階段都有其特殊的方式,但通常,當(dāng)事件循環(huán)進(jìn)入給定階段時(shí),它將執(zhí)行特定于該階段的任何操作,然后在該階段的隊(duì)列中執(zhí)行回調(diào),直到隊(duì)列耗盡或最大回調(diào)數(shù)量為止 。 當(dāng)隊(duì)列耗盡或達(dá)到回調(diào)限制時(shí),事件循環(huán)將移至下一階段,依此類推。

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

注意:Windows和Unix / Linux實(shí)現(xiàn)之間存在輕微差異,但這對(duì)于此演示并不重要。 最重要的部分在這里。 實(shí)際上有七到八個(gè)步驟,但我們關(guān)心的是 - Node.js實(shí)際使用的那些 - 是上面那些。

階段概述

  • timer : 此階段執(zhí)行 setTimeout() 和 setInterval() 調(diào)度的回調(diào)
  • pending callbacks : 執(zhí)行延遲到下一個(gè)循環(huán)迭代的I/O回調(diào)
  • idle, prepare : 只用于內(nèi)部
  • poll : 檢索新的I/O事件; 執(zhí)行與I/O相關(guān)的回調(diào)(幾乎所有回調(diào)都是帶有異常的 close callbacks , timers 和 setImmediate() 調(diào)度的回調(diào)); node將在適當(dāng)?shù)臅r(shí)候阻塞在這里
  • check : 這里調(diào)用 setImmediate() 回調(diào)函數(shù)
  • close callbacks : 一些 close callbacks, 例如. socket.on(‘close', …)

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

階段細(xì)節(jié)

定時(shí)器(timer)

計(jì)時(shí)器在一個(gè)回調(diào)執(zhí)行完之后指定閾值,而不是人們希望的確切時(shí)間去執(zhí)行。 定時(shí)器回調(diào)將在指定的時(shí)間過去后盡早安排; 但是,操作系統(tǒng)調(diào)度或其他回調(diào)的運(yùn)行可能會(huì)延遲它們。

注意:從技術(shù)上講,控制何時(shí)執(zhí)行定時(shí)器。

例如,假設(shè)您計(jì)劃在100毫秒后執(zhí)行 timeout ,然后您的腳本將異步讀取一個(gè)耗時(shí)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ì)列( fs.readFile() 尚未完成),因此它將等待剩余的ms數(shù),直到達(dá)到最快的計(jì)時(shí)器閾值。 當(dāng)它等待95毫秒傳遞時(shí), fs.readFile() 完成讀取文件,并且其完成需要10毫秒的回調(diào)被添加到輪詢隊(duì)列并執(zhí)行。 當(dāng)回調(diào)結(jié)束時(shí),隊(duì)列中不再有回調(diào),因此事件循環(huán)將看到已達(dá)到最快定時(shí)器的閾值,然后回繞到定時(shí)器階段以執(zhí)行定時(shí)器的回調(diào)。 在此示例中,您將看到正在調(diào)度的計(jì)時(shí)器與正在執(zhí)行的回調(diào)之間的總延遲將為105毫秒。

注意:為了防止輪詢階段使事件循環(huán)挨餓,libuv(實(shí)現(xiàn)Node.js事件循環(huán)的C庫和平臺(tái)的所有異步行為)在停止輪詢之前也為事件提供了固定的最大值(取決于系統(tǒng))。

等待回調(diào)(pending callbacks)

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

輪詢(poll)

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

1.計(jì)算它阻塞和輪詢I / O的時(shí)間,然后
2.處理輪詢隊(duì)列中的事件。

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

  • 如果輪詢隊(duì)列不為空,則事件循環(huán)將遍歷回調(diào)隊(duì)列并且同步執(zhí)行,直到隊(duì)列已執(zhí)行完或者達(dá)到系統(tǒng)相關(guān)的固定限制。
  • 如果輪詢隊(duì)列為空,則會(huì)發(fā)生以下兩種情況之一:

setImmediate()
setImmediate()

檢查(check)

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

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

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

關(guān)閉回調(diào)(close callbacks)

如果套接字或句柄突然關(guān)閉( 例如socket.destroy() ),則在此階段將發(fā)出 'close' 事件。 否則它將通過 process.nextTick() 發(fā)出。

setImmediate() vs setTimeout()

setImmediate 和 setTimeout() 類似,但根據(jù)它們的調(diào)用時(shí)間以不同的方式運(yùn)行。

setImmediate()
setTimeout()

執(zhí)行定時(shí)器的順序?qū)⒏鶕?jù)調(diào)用它們的上下文而有所不同。 如果從主模塊中調(diào)用兩者,則時(shí)間將受到進(jìn)程性能的限制(可能受到計(jì)算機(jī)上運(yùn)行的其他應(yīng)用程序的影響)。

例如,如果我們運(yùn)行不在I / O周期內(nèi)的以下腳本(即主模塊),則執(zhí)行兩個(gè)定時(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

但是,如果在I / O周期內(nèi)移動(dòng)兩個(gè)調(diào)用,則始終首先執(zhí)行立即回調(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() 而不是 setTimeout() 的主要優(yōu)點(diǎn)是 setImmediate() 將始終在任何定時(shí)器之前執(zhí)行(如果在I / O周期內(nèi)調(diào)度),與存在多少定時(shí)器無關(guān)。

process.nextTick()

理解 process.nextTick()

您可能已經(jīng)注意到 process.nextTick() 沒有顯示在圖中,即使它是異步API的一部分。 這是因?yàn)?process.nextTick() 在技術(shù)上不是事件循環(huán)的一部分。 相反, nextTickQueue 將在當(dāng)前操作完成后處理,而不管事件循環(huán)的當(dāng)前階段如何。

回顧一下我們的圖表,無論何時(shí)在給定階段調(diào)用 process.nextTick() ,傳遞給 process.nextTick() 的所有回調(diào)都將在事件循環(huán)繼續(xù)之前得到解決。 這可能會(huì)產(chǎn)生一些不好的情況, 因?yàn)樗试S您通過進(jìn)行遞歸的 process.nextTick() 調(diào)用來“餓死”您的I / O, 這會(huì)阻止事件循環(huán)到達(dá)輪詢階段。

為什么會(huì)被允許?

為什么這樣的東西會(huì)被包含在Node.js中? 其中一部分是一種設(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)。 最近更新的API允許將參數(shù)傳遞給 process.nextTick() ,允許它將回調(diào)后傳遞的任何參數(shù)作為參數(shù)傳播到回調(diào),因此您不必嵌套函數(shù)。

我們正在做的是將錯(cuò)誤傳回給用戶,但只有在我們?cè)试S其余的用戶代碼執(zhí)行之后。 通過使用 process.nextTick() ,我們保證 apiCall() 始終在用戶代碼的其余部分之后和允許事件循環(huán)繼續(xù)之前運(yùn)行其回調(diào)。 為了實(shí)現(xiàn)這一點(diǎn),允許JS調(diào)用堆棧展開然后立即執(zhí)行提供的回調(diào),這允許一個(gè)人對(duì) process.nextTick() 進(jìn)行遞歸調(diào)用而不會(huì)達(dá)到 RangeError :超出v8的最大調(diào)用堆棧大小。

這種理念可能會(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í)際上是同步操作的。 調(diào)用它時(shí),在事件循環(huán)的同一階段調(diào)用提供給 someAsyncApiCall() 的回調(diào),因?yàn)?someAsyncApiCall() 實(shí)際上不會(huì)異步執(zhí)行任何操作。 因此,回調(diào)嘗試引用bar,即使它在范圍內(nèi)可能沒有該變量,因?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ù)之前,向用戶警告錯(cuò)誤可能是有用的。 以下是使用 process.nextTick() 的前一個(gè)示例:

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í),端口立即綁定。 因此,可以立即調(diào)用 'listening' 回調(diào)。 問題是那時(shí)候不會(huì)設(shè)置 .on('listening') 回調(diào)。

為了解決這個(gè)問題, 'listening' 事件在 nextTick() 中排隊(duì),以允許腳本運(yùn)行完成。 這允許用戶設(shè)置他們想要的任何事件處理程序。

process.nextTick() vs setImmediate()

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

process.nextTick()
setImmediate()

實(shí)質(zhì)上,應(yīng)該交換名稱。 process.nextTick() 比 setImmediate() 更快地觸發(fā),但這是過去創(chuàng)造的,不太可能改變。 進(jìn)行此切換會(huì)破壞npm上的大部分包。 每天都會(huì)添加更多新模塊,這意味著我們每天都在等待,更多的潛在破損發(fā)生。 雖然它們令人困惑,但自身的叫法不會(huì)改變。

我們建議開發(fā)人員在所有情況下都使用 setImmediate() ,因?yàn)樗菀淄评恚ú⑶宜鼘?dǎo)致代碼與更廣泛的環(huán)境兼容,如瀏覽器JS。)

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

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

  • 允許用戶處理錯(cuò)誤,清除任何不需要的資源,或者在事件循環(huán)繼續(xù)之前再次嘗試請(qǐng)求。
  • 有時(shí)需要允許回調(diào)在調(diào)用堆棧展開之后但在事件循環(huán)繼續(xù)之前運(yùn)行。

一個(gè)例子是匹配用戶的期望。 簡(jiǎn)單的例子:

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

假設(shè) listen() 在事件循環(huán)開始時(shí)運(yùn)行,但是監(jiān)聽回調(diào)放在 setImmediate() 中。 除非傳遞主機(jī)名,否則將立即綁定到端口。 要使事件循環(huán)繼續(xù),它必須達(dá)到輪詢階段,這意味著可能已經(jīng)接收到連接的非零概率允許在偵聽事件之前觸發(fā)連接事件。

另一個(gè)例子是運(yùn)行一個(gè)函數(shù)構(gòu)造函數(shù),比如繼承自 EventEmitter ,它想在構(gòu)造函數(shù)中調(diào)用一個(gè)事件:

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)槟_本將不會(huì)處理到用戶為該事件分配回調(diào)的位置。 因此,在構(gòu)造函數(shù)本身中,您可以使用 process.nextTick() 來設(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!');
});

總結(jié)

以上所述是小編給大家介紹的深入淺析Node.js 事件循環(huán)、定時(shí)器和process.nextTick() ,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!

相關(guān)文章

  • 一文詳解NPM如何換源

    一文詳解NPM如何換源

    在每一次的實(shí)際開發(fā)過程中我們都會(huì)下載相關(guān)的依賴包,最官方的是 npm,但是該服務(wù)器對(duì)于國內(nèi)開發(fā)者來說,下載起來是比較慢的,所以我們需要換源,下面這篇文章主要給大家介紹了關(guān)于NPM如何換源的相關(guān)資料,需要的朋友可以參考下
    2023-02-02
  • node.js 用socket實(shí)現(xiàn)聊天的示例代碼

    node.js 用socket實(shí)現(xiàn)聊天的示例代碼

    本篇文章主要介紹了node.js 用socket實(shí)現(xiàn)聊天的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-10-10
  • nodejs使用Sequelize框架操作數(shù)據(jù)庫的實(shí)現(xiàn)

    nodejs使用Sequelize框架操作數(shù)據(jù)庫的實(shí)現(xiàn)

    這篇文章主要介紹了nodejs使用Sequelize框架操作數(shù)據(jù)庫的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • Nodejs 數(shù)組的隊(duì)列以及forEach的應(yīng)用詳解

    Nodejs 數(shù)組的隊(duì)列以及forEach的應(yīng)用詳解

    這篇文章主要介紹了Nodejs 數(shù)組的隊(duì)列以及forEach的應(yīng)用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • 用nodejs的實(shí)現(xiàn)原理和搭建服務(wù)器(動(dòng)態(tài))

    用nodejs的實(shí)現(xiàn)原理和搭建服務(wù)器(動(dòng)態(tài))

    下面小編就為大家?guī)硪黄胣odejs的實(shí)現(xiàn)原理和搭建服務(wù)器(動(dòng)態(tài))。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-08-08
  • ChatGPT編程秀之最小元素的設(shè)計(jì)示例詳解

    ChatGPT編程秀之最小元素的設(shè)計(jì)示例詳解

    這篇文章主要為大家介紹了ChatGPT編程秀之最小元素的設(shè)計(jì)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • node.js中的Socket.IO使用實(shí)例

    node.js中的Socket.IO使用實(shí)例

    這篇文章主要介紹了node.js中的Socket.IO使用實(shí)例,本文講解了Socket.IO的安裝、結(jié)合express來構(gòu)建服務(wù)器、基本使用方法及一個(gè)網(wǎng)絡(luò)聊天室的完整案例,需要的朋友可以參考下
    2014-11-11
  • 150行Node.js實(shí)現(xiàn)的dns代理工具

    150行Node.js實(shí)現(xiàn)的dns代理工具

    這篇文章主要介紹了150行Node.js實(shí)現(xiàn)的dns代理工具,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-08-08
  • Node.js常用三大模塊之path模塊

    Node.js常用三大模塊之path模塊

    在node.js中常用的三大模塊中還有一個(gè)是path模塊,主要是為了便利用戶處理路徑問題,如果我們?cè)谝胛募臅r(shí)候經(jīng)常會(huì)受到../和./的干擾,那么這個(gè)path模塊將會(huì)很好的幫助你
    2022-09-09
  • 從零揭秘npm install的黑科技

    從零揭秘npm install的黑科技

    通過npm package manager來安裝和管理包是我們最為常見的方式之一,本文將從淺入深地帶大家剖析一下npm install的執(zhí)行過程,感興趣的可以學(xué)習(xí)一下
    2023-05-05

最新評(píng)論