溫馨提示×

溫馨提示×

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

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

Node.js中的定時(shí)器是什么

發(fā)布時(shí)間:2020-12-04 14:01:45 來源:億速云 閱讀:140 作者:小新 欄目:web開發(fā)

小編給大家分享一下Node.js中的定時(shí)器是什么,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

timer 用于安排函數(shù)在未來某個(gè)時(shí)間點(diǎn)被調(diào)用,Node.js 中的定時(shí)器函數(shù)實(shí)現(xiàn)了與 Web 瀏覽器提供的定時(shí)器 API 類似的 API,但是使用了事件循環(huán)實(shí)現(xiàn),Node.js 中有四個(gè)相關(guān)的方法

  • setTimeout(callback, delay[, ...args])

  • setInterval(callback[, ...args])

  • setImmediate(callback[, ...args])

  • process.nextTick(callback[, ...args])

前兩個(gè)含義和 web 上的是一致的,后兩個(gè)是 Node.js 獨(dú)有的,效果看起來就是 setTimeout(callback, 0),在 Node.js 編程中使用的最多

Node.js 不保證回調(diào)被觸發(fā)的確切時(shí)間,也不保證它們的順序,回調(diào)會在盡可能接近指定的時(shí)間被調(diào)用。setTimeout 當(dāng) delay 大于 2147483647 或小于 1 時(shí),則 delay 將會被設(shè)置為 1, 非整數(shù)的 delay 會被截?cái)酁檎麛?shù)

奇怪的執(zhí)行順序

看一個(gè)示例,用幾種方法分別異步打印一個(gè)數(shù)字

setImmediate(console.log, 1);
setTimeout(console.log, 1, 2);
Promise.resolve(3).then(console.log);
process.nextTick(console.log, 4);
console.log(5);

會打印 5 4 3 2 1 或者 5 4 3 1 2

同步 & 異步

第五行是同步執(zhí)行,其它都是異步的

setImmediate(console.log, 1);
setTimeout(console.log, 1, 2);
Promise.resolve(3).then(console.log);
process.nextTick(console.log, 4);
/****************** 同步任務(wù)和異步任務(wù)的分割線 ********************/
console.log(5);

所以先打印 5,這個(gè)很好理解,剩下的都是異步操作,Node.js 按照什么順序執(zhí)行呢?

event loop

Node.js 啟動后會初始化事件輪詢,過程中可能處理異步調(diào)用、定時(shí)器調(diào)度和 process.nextTick(),然后開始處理event loop。官網(wǎng)中有這樣一張圖用來介紹 event loop 操作順序

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

event loop 的每個(gè)階段都有一個(gè)任務(wù)隊(duì)列,當(dāng) event loop 進(jìn)入給定的階段時(shí),將執(zhí)行該階段的任務(wù)隊(duì)列,直到隊(duì)列清空或執(zhí)行的回調(diào)達(dá)到系統(tǒng)上限后,才會轉(zhuǎn)入下一個(gè)階段,當(dāng)所有階段被順序執(zhí)行一次后,稱 event loop 完成了一個(gè) tick

異步操作都被放到了下一個(gè) event loop tick 中,process.nextTick 在進(jìn)入下一次 event loop tick 之前執(zhí)行,所以肯定在其它異步操作之前

setImmediate(console.log, 1);
setTimeout(console.log, 1, 2);
Promise.resolve(3).then(console.log);
/****************** 下次 event loop tick 分割線 ********************/
process.nextTick(console.log, 4);
/****************** 同步任務(wù)和異步任務(wù)的分割線 ********************/
console.log(5);

各個(gè)階段主要任務(wù)

  • timers:執(zhí)行 setTimeout、setInterval 回調(diào)

  • pending callbacks:執(zhí)行 I/O(文件、網(wǎng)絡(luò)等) 回調(diào)

  • idle, prepare:僅供系統(tǒng)內(nèi)部調(diào)用

  • poll:獲取新的 I/O 事件,執(zhí)行相關(guān)回調(diào),在適當(dāng)條件下把阻塞 node

  • check:setImmediate 回調(diào)在此階段執(zhí)行

  • close callbacks:執(zhí)行 socket 等的 close 事件回調(diào)

日常開發(fā)中絕大部分異步任務(wù)都是在 timers、poll、check 階段處理的

timers

Node.js 會在 timers 階段檢查是否有過期的 timer,如果存在則把回調(diào)放到 timer 隊(duì)列中等待執(zhí)行,Node.js 使用單線程,受限于主線程空閑情況和機(jī)器其它進(jìn)程影響,并不能保證 timer 按照精確時(shí)間執(zhí)行
定時(shí)器主要有兩種

  • Immediate

  • Timeout

Immediate 類型的計(jì)時(shí)器回調(diào)會在 check 階段被調(diào)用,Timeout 計(jì)時(shí)器會在設(shè)定的時(shí)間過期后盡快的調(diào)用回調(diào),但

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

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

多次執(zhí)行會發(fā)現(xiàn)打印的順序不一樣

poll

poll 階段主要有兩個(gè)任務(wù)

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

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

當(dāng)event loop進(jìn)入 poll 階段且沒有被調(diào)度的計(jì)時(shí)器時(shí)

  • 如果 poll 隊(duì)列不是空的 ,event loop 將循環(huán)訪問回調(diào)隊(duì)列并同步執(zhí)行,直到隊(duì)列已用盡或者達(dá)到了系統(tǒng)或達(dá)到最大回調(diào)數(shù)
  • 如果 poll 隊(duì)列是空的
    • 如果有 setImmediate() 任務(wù),event loop 會在結(jié)束 poll 階段后進(jìn)入 check 階段
    • 如果沒有 setImmediate()任務(wù),event loop 阻塞在 poll 階段等待回調(diào)被添加到隊(duì)列中,然后立即執(zhí)行

一旦 poll 隊(duì)列為空,event loop 將檢查 timer 隊(duì)列是否為空,如果非空則進(jìn)入下一輪 event loop

上面提到了如果在不同的 I/O 里,不能確定 setTimeout 和 setImmediate 的執(zhí)行順序,但如果 setTimeout 和 setImmediate 在一個(gè) I/O 回調(diào)里,肯定是 setImmediate 先執(zhí)行,因?yàn)樵?poll 階段檢查到有 setImmediate() 任務(wù),event loop 直接進(jìn)入 check 階段執(zhí)行 setImmediate 回調(diào)

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

check

在該階段執(zhí)行 setImmediate 回調(diào)

為什么 Promise.then 比 setTimeout 早一些

前端同學(xué)肯定都聽說過 micoTask 和 macroTask,Promise.then 屬于 microTask,在瀏覽器環(huán)境下 microTask 任務(wù)會在每個(gè) macroTask 執(zhí)行最末端調(diào)用

在 Node.js 環(huán)境下 microTask 會在每個(gè)階段完成之間調(diào)用,也就是每個(gè)階段執(zhí)行最后都會執(zhí)行一下 microTask 隊(duì)列

setImmediate(console.log, 1);
setTimeout(console.log, 1, 2);
/****************** microTask 分割線 ********************/
Promise.resolve(3).then(console.log); // microTask 分割線
/****************** 下次 event loop tick 分割線 ********************/
process.nextTick(console.log, 4);
/****************** 同步任務(wù)和異步任務(wù)的分割線 ********************/
console.log(5);

setImmediate VS process.nextTick

setImmediate 聽起來是立即執(zhí)行,process.nextTick 聽起來是下一個(gè)時(shí)鐘執(zhí)行,為什么效果是反過來的?這就要從那段不堪回首的歷史講起

最開始的時(shí)候只有 process.nextTick 方法,沒有 setImmediate 方法,通過上面的分析可以看出來任何時(shí)候調(diào)用 process.nextTick(),nextTick 會在 event loop 之前執(zhí)行,直到 nextTick 隊(duì)列被清空才會進(jìn)入到下一 event loop,如果出現(xiàn) process.nextTick 的遞歸調(diào)用程序沒有被正確結(jié)束,那么 IO 的回調(diào)將沒有機(jī)會被執(zhí)行

const fs = require('fs');

fs.readFile('a.txt', (err, data) => {
	console.log('read file task done!');
});

let i = 0;
function test(){
	if(i++ < 999999) {
  	console.log(`process.nextTick ${i}`);
    process.nextTick(test);
  }
}
test();

執(zhí)行程序?qū)⒎祷?/p>

nextTick 1
nextTick 2
...
...
nextTick 999999
read file task done!

于是乎需要一個(gè)不這么 bug 的調(diào)用,setImmediate 方法出現(xiàn)了,比較令人費(fèi)解的是在 process.nextTick 起錯(cuò)名字的情況下,setImmediate 也用了一個(gè)錯(cuò)誤的名字以示區(qū)分。。。

那么是不是編程中應(yīng)該杜絕使用  process.nextTick 呢?官方推薦大部分時(shí)候應(yīng)該使用 setImmediate,同時(shí)對 process.nextTick 的最大調(diào)用堆棧做了限制,但 process.nextTick 的調(diào)用機(jī)制確實(shí)也能為我們解決一些棘手的問題

  • 允許用戶在 even tloop 開始之前 處理異常、執(zhí)行清理任務(wù)

  • 允許回調(diào)在調(diào)用棧 unwind 之后,下次 event loop 開始之前執(zhí)行

一個(gè)類繼承了 EventEmitter,而且想在實(shí)例化的時(shí)候觸發(fā)一個(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ù)執(zhí)行 this.emit('event') 會導(dǎo)致事件觸發(fā)比事件回調(diào)函數(shù)綁定早,使用 process.nextTick 可以輕松實(shí)現(xiàn)預(yù)期效果

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.js中的定時(shí)器是什么”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

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

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

AI