成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Node.js 指南(Node.js事件循環(huán)、定時(shí)器和process.nextTick())

pingink / 2604人閱讀

摘要:檢索新的事件執(zhí)行與相關(guān)的回調(diào)幾乎所有,除了由定時(shí)器調(diào)度的一些和將在適當(dāng)?shù)臅r(shí)候在這里阻塞。在事件循環(huán)的每次運(yùn)行之間,檢查它是否在等待任何異步或定時(shí)器,如果沒有,則徹底關(guān)閉。

Node.js事件循環(huán)、定時(shí)器和process.nextTick() 什么是事件循環(huán)?

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

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

事件循環(huán)解釋

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

下面的圖解顯示了事件循環(huán)操作順序的簡要概述。

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

注意:每個(gè)框都將被稱為事件循環(huán)的“階段”。

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

由于任何這些操作都可以調(diào)度更多操作,并且在輪詢階段處理的新事件由內(nèi)核排隊(duì),輪詢事件可以在處理輪詢事件時(shí)排隊(duì),因此,長時(shí)間運(yùn)行的回調(diào)可以允許輪詢階段的運(yùn)行時(shí)間遠(yuǎn)遠(yuǎn)超過定時(shí)器的閾值,有關(guān)詳細(xì)信息,請參閱timerspoll部分。

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

階段概述

timers:此階段執(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)(幾乎所有,除了close callbacks、由定時(shí)器調(diào)度的一些和setImmediate());node將在適當(dāng)?shù)臅r(shí)候在這里阻塞。

check:這里調(diào)用setImmediate()回調(diào)函數(shù)。

close callbacks:一些關(guān)閉回調(diào),例如socket.on("close", ...)。

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

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

定時(shí)器指定閾值,在該閾值之后可以執(zhí)行提供的回調(diào)而不是人們希望它執(zhí)行的確切時(shí)間,定時(shí)器回調(diào)將在指定的時(shí)間過后可以調(diào)度,但是,操作系統(tǒng)調(diào)度或其他回調(diào)的運(yùn)行可能會延遲它們。

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

例如,假設(shè)你在100毫秒閾值后調(diào)度執(zhí)行超時(shí),那么你的腳本將異步讀取一個(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á)到最快的定時(shí)器閾值,當(dāng)它等待95毫秒通過,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)度的定時(shí)器與正在執(zhí)行的回調(diào)之間的總延遲將為105毫秒。

注意:為了防止輪詢階段耗盡事件循環(huán),libuv(實(shí)現(xiàn)Node.js事件循環(huán)的C庫以及平臺的所有異步行為)在停止輪詢更多事件之前,還具有硬性最大值(取決于系統(tǒng))。

pending callbacks

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

poll

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

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

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

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

如果輪詢隊(duì)列不為空,則事件循環(huán)將遍歷其同步執(zhí)行它們的回調(diào)隊(duì)列,直到隊(duì)列已用盡,或者達(dá)到系統(tǒng)相關(guān)的硬限制。

如果輪詢隊(duì)列為空,則會發(fā)生以下兩種情況之一:

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

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

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

check

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

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

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

close callbacks

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

setImmediate()setTimeout()

setImmediate()setTimeout()類似,但行為方式不同,取決于他們何時(shí)被調(diào)用。

setImmediate()用于在當(dāng)前輪詢階段完成后執(zhí)行腳本。

setTimeout()調(diào)度在經(jīng)過最小閾值(以ms為單位)后運(yùn)行腳本。

執(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

但是,如果移動兩個(gè)調(diào)用到I/O周期內(nèi),則始終首先執(zhí)行immediate回調(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)?b>process.nextTick()在技術(shù)上不是事件循環(huán)的一部分,相反,nextTickQueue將在當(dāng)前操作完成后處理,而不管事件循環(huán)的當(dāng)前階段如何。

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

為什么會被允許?

為什么這樣的東西會被包含在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ù)檢查,如果它不正確,它會將錯(cuò)誤傳遞給回調(diào),最近更新的API允許將參數(shù)傳遞給process.nextTick(),允許它將回調(diào)后傳遞的任何參數(shù)作為參數(shù)傳播到回調(diào),因此你不必嵌套函數(shù)。

我們正在做的是將錯(cuò)誤傳回給用戶,但只有在我們允許其余的用戶代碼執(zhí)行之后,通過使用process.nextTick(),我們保證apiCall()始終在用戶代碼的其余部分之后并且在允許事件循環(huán)之前運(yùn)行其回調(diào),為了實(shí)現(xiàn)這一點(diǎn),JS調(diào)用堆棧允許放松然后立即執(zhí)行提供的回調(diào),這允許一個(gè)人對process.nextTick()進(jìn)行遞歸調(diào)用而不會達(dá)到RangeError: Maximum call stack size exceeded from v8。

這種理念可能會導(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í)際上是同步操作的,當(dāng)它被調(diào)用時(shí),提供給someAsyncApiCall()的回調(diào)在事件循環(huán)的同一階段被調(diào)用,因?yàn)?b>someAsyncApiCall()實(shí)際上不會異步執(zhí)行任何操作。因此,回調(diào)嘗試引用bar,即使它在范圍內(nèi)可能沒有該變量,因?yàn)樵撃_本無法運(yùn)行完成。

通過將回調(diào)放在process.nextTick()中,腳本仍然能夠運(yùn)行完成,允許所有變量、函數(shù)等,在調(diào)用回調(diào)之前進(jìn)行初始化。它還具有不允許事件循環(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í)候不會設(shè)置.on("listening")回調(diào)。

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

process.nextTick() vs setImmediate()

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

process.nextTick()在同一階段立即觸發(fā)。

setImmediate()在事件循環(huán)的后續(xù)迭代或"tick"觸發(fā)。

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

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

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

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

允許用戶處理錯(cuò)誤、清除任何不需要的資源,或者在事件循環(huán)繼續(xù)之前再次嘗試請求。

有時(shí),在調(diào)用堆棧已解除但在事件循環(huán)繼續(xù)之前,必須允許回調(diào)運(yùn)行。

一個(gè)例子是匹配用戶的期望,簡單的例子:

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á)到輪詢階段,這意味著有一個(gè)非零的可能性,連接可能已經(jīng)被接收,允許連接事件在監(jiān)聽事件之前被觸發(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)槟_本還沒有處理到用戶為該事件分配回調(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!");
});
上一篇:阻塞與非阻塞概述 下一篇:不要阻塞事件循環(huán)(或工作池)

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/108572.html

相關(guān)文章

  • Node.js 指南Node.js中的時(shí)器

    摘要:中的定時(shí)器中的模塊包含在一段時(shí)間后執(zhí)行代碼的函數(shù),定時(shí)器不需要通過導(dǎo)入,因?yàn)樗蟹椒ǘ伎梢栽谌址秶鷥?nèi)模擬瀏覽器,要完全了解何時(shí)執(zhí)行定時(shí)器功能,最好先閱讀事件循環(huán)。 Node.js中的定時(shí)器 Node.js中的Timers模塊包含在一段時(shí)間后執(zhí)行代碼的函數(shù),定時(shí)器不需要通過require()導(dǎo)入,因?yàn)樗蟹椒ǘ伎梢栽谌址秶鷥?nèi)模擬瀏覽器JavaScript API,要完全了解何時(shí)執(zhí)行定...

    econi 評論0 收藏0
  • Node.js中的事件循環(huán)(Event Loop),計(jì)時(shí)器(Timers)以及process.nex

    摘要:回調(diào)函數(shù)執(zhí)行幾乎所有的回調(diào)函數(shù),除了關(guān)閉回調(diào)函數(shù),定時(shí)器計(jì)劃的回調(diào)函數(shù)和。輪詢此階段有兩個(gè)主要的功能執(zhí)行已過時(shí)的定時(shí)器腳本處理輪詢隊(duì)列中的事件。一旦輪詢隊(duì)列為空,事件循環(huán)將檢查已達(dá)到時(shí)間閾值的定時(shí)器。 什么是事件循環(huán)(Event Loop)? 事件環(huán)使得Node.js可以執(zhí)行非阻塞I/O 操作,只要有可能就將操作卸載到系統(tǒng)內(nèi)核,盡管JavaScript是單線程的。 由于大多數(shù)現(xiàn)代(終端...

    KoreyLee 評論0 收藏0
  • Node.js】理解事件循環(huán)機(jī)制

    摘要:前沿是基于引擎的運(yùn)行環(huán)境具有事件驅(qū)動非阻塞等特點(diǎn)結(jié)合具有網(wǎng)絡(luò)編程文件系統(tǒng)等服務(wù)端的功能用庫進(jìn)行異步事件處理線程的單線程含義實(shí)際上說的是執(zhí)行同步代碼的主線程一個(gè)程序的啟動不止是分配了一個(gè)線程,而是我們只能在一個(gè)線程執(zhí)行代碼當(dāng)出現(xiàn)資源調(diào)用連接等 前沿 Node.js 是基于V8引擎的javascript運(yùn)行環(huán)境. Node.js具有事件驅(qū)動, 非阻塞I/O等特點(diǎn). 結(jié)合Node API, ...

    Riddler 評論0 收藏0
  • Node中的事件循環(huán)異步API

    摘要:異步在中,是在單線程中執(zhí)行的沒錯(cuò),但是內(nèi)部完成工作的另有線程池,使用一個(gè)主進(jìn)程和多個(gè)線程來模擬異步。在事件循環(huán)中,觀察者會不斷的找到線程池中已經(jīng)完成的請求對象,從中取出回調(diào)函數(shù)和數(shù)據(jù)并執(zhí)行。 1. 介紹 單線程編程會因阻塞I/O導(dǎo)致硬件資源得不到更優(yōu)的使用。多線程編程也因?yàn)榫幊讨械乃梨i、狀態(tài)同步等問題讓開發(fā)人員頭痛。Node在兩者之間給出了它的解決方案:利用單線程,遠(yuǎn)離多線程死鎖、狀態(tài)...

    atinosun 評論0 收藏0
  • 大話javascript 4期:事件循環(huán)(3)

    摘要:令人困惑的是,文檔中稱,指定的回調(diào)函數(shù),總是排在前面。另外,由于指定的回調(diào)函數(shù)是在本次事件循環(huán)觸發(fā),而指定的是在下次事件循環(huán)觸發(fā),所以很顯然,前者總是比后者發(fā)生得早,而且執(zhí)行效率也高因?yàn)椴挥脵z查任務(wù)隊(duì)列。 一、定時(shí)器 除了放置異步任務(wù)的事件,任務(wù)隊(duì)列還可以放置定時(shí)事件,即指定某些代碼在多少時(shí)間之后執(zhí)行。這叫做定時(shí)器(timer)功能,也就是定時(shí)執(zhí)行的代碼。 定時(shí)器功能主要由setTim...

    liujs 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<