亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

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

pingink / 2655人閱讀

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

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

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

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

事件循環(huán)解釋

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

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

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

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

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

由于任何這些操作都可以調度更多操作,并且在輪詢階段處理的新事件由內核排隊,輪詢事件可以在處理輪詢事件時排隊,因此,長時間運行的回調可以允許輪詢階段的運行時間遠遠超過定時器的閾值,有關詳細信息,請參閱timerspoll部分。

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

階段概述

timers:此階段執(zhí)行由setTimeout()setInterval()調度的回調。

pending callbacks:執(zhí)行延遲到下一個循環(huán)迭代的I/O回調。

idle, prepare:僅在內部使用。

poll:檢索新的I/O事件;執(zhí)行與I/O相關的回調(幾乎所有,除了close callbacks、由定時器調度的一些和setImmediate());node將在適當?shù)臅r候在這里阻塞。

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

close callbacks:一些關閉回調,例如socket.on("close", ...)。

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

階段的細節(jié) timers

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

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

例如,假設你在100毫秒閾值后調度執(zhí)行超時,那么你的腳本將異步讀取一個耗時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
  }
});

當事件循環(huán)進入輪詢階段時,它有一個空隊列(fs.readFile()尚未完成),所以它將等待剩余的ms數(shù),直到達到最快的定時器閾值,當它等待95毫秒通過,fs.readFile()完成了讀取文件,其需要10毫秒完成的回調被添加到輪詢隊列并執(zhí)行,當回調結束時,隊列中不再有回調,因此事件循環(huán)將看到已達到最快定時器的閾值然后回到定時器階段以執(zhí)行定時器的回調,在此示例中,你將看到正在調度的定時器與正在執(zhí)行的回調之間的總延遲將為105毫秒。

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

pending callbacks

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

poll

輪詢階段有兩個主要功能:

計算它應該阻塞和輪詢I/O的時間。

然后處理輪詢隊列中的事件。

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

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

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

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

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

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

check

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

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

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

close callbacks

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

setImmediate()setTimeout()

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

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

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

執(zhí)行定時器的順序將根據(jù)調用它們的上下文而有所不同,如果從主模塊中調用兩者,則時間將受到進程性能的限制(可能受到計算機上運行的其他應用程序的影響)。

例如,如果我們運行不在I/O周期內的以下腳本(即主模塊),則執(zhí)行兩個定時器的順序是不確定的,因為它受進程性能的約束:

// 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周期內,則始終首先執(zhí)行immediate回調:

// 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)點是setImmediate()將始終在任何定時器之前執(zhí)行(如果在I/O周期內調度),與存在多少定時器無關。

process.nextTick() 理解process.nextTick()

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

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

為什么會被允許?

為什么這樣的東西會被包含在Node.js中?其中一部分是一種設計理念,其中API應該始終是異步的,即使它不是必須的,以此代碼段為例:

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

該片段進行參數(shù)檢查,如果它不正確,它會將錯誤傳遞給回調,最近更新的API允許將參數(shù)傳遞給process.nextTick(),允許它將回調后傳遞的任何參數(shù)作為參數(shù)傳播到回調,因此你不必嵌套函數(shù)。

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

這種理念可能會導致一些潛在的問題,以此片段為例:

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()定義為具有異步簽名,但它實際上是同步操作的,當它被調用時,提供給someAsyncApiCall()的回調在事件循環(huán)的同一階段被調用,因為someAsyncApiCall()實際上不會異步執(zhí)行任何操作。因此,回調嘗試引用bar,即使它在范圍內可能沒有該變量,因為該腳本無法運行完成。

通過將回調放在process.nextTick()中,腳本仍然能夠運行完成,允許所有變量、函數(shù)等,在調用回調之前進行初始化。它還具有不允許事件循環(huán)繼續(xù)的優(yōu)點,在允許事件循環(huán)繼續(xù)之前,向用戶警告錯誤可能是有用的,以下是使用process.nextTick()的前一個示例:

let bar;

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

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

bar = 1;

這是另一個真實世界的例子:

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

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

僅傳遞端口時,端口立即綁定,因此,可以立即調用"listening"回調,問題是那時候不會設置.on("listening")回調。

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

process.nextTick() vs setImmediate()

就用戶而言,我們有兩個類似的調用,但它們的名稱令人困惑。

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

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

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

我們建議開發(fā)人員在所有情況下都使用setImmediate(),因為它更容易推理(并且它使代碼與更廣泛的環(huán)境兼容,如瀏覽器JS)。

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

主要有兩個原因:

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

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

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

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

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

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

另一個例子是運行一個函數(shù)構造函數(shù),比如繼承自EventEmitter,它想在構造函數(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!");
});

你無法立即從構造函數(shù)中發(fā)出事件,因為腳本還沒有處理到用戶為該事件分配回調的位置,因此,在構造函數(shù)本身中,你可以使用process.nextTick()來設置回調以在構造函數(shù)完成后發(fā)出事件,從而提供預期的結果:

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)(或工作池)

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

轉載請注明本文地址:http://www.ezyhdfw.cn/yun/108572.html

相關文章

  • Node.js 指南Node.js中的時器

    摘要:中的定時器中的模塊包含在一段時間后執(zhí)行代碼的函數(shù),定時器不需要通過導入,因為所有方法都可以在全局范圍內模擬瀏覽器,要完全了解何時執(zhí)行定時器功能,最好先閱讀事件循環(huán)。 Node.js中的定時器 Node.js中的Timers模塊包含在一段時間后執(zhí)行代碼的函數(shù),定時器不需要通過require()導入,因為所有方法都可以在全局范圍內模擬瀏覽器JavaScript API,要完全了解何時執(zhí)行定...

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

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

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

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

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

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

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

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

    liujs 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<