首頁  >  文章  >  web前端  >  了解Node.js中的定時器

了解Node.js中的定時器

青灯夜游
青灯夜游轉載
2020-12-03 18:11:133177瀏覽

了解Node.js中的定時器

相關推薦:《node js教程

#timer 用於安排函數在未來某個時間點被調用,Node .js 中的定時器函數實作了與Web 瀏覽器提供的定時器API 類似的API,但是使用了事件循環實現,Node.js 中有四個相關的方法

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

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

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

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

前兩個意義和web 上的是一致的,後兩個是Node.js 獨有的,效果看起來就是setTimeout(callback, 0),在Node.js 程式設計中使用的最多

Node.js 不保證回調被觸發的確切時間,也不保證它們的順序,回調會在盡可能接近指定的時間被呼叫。 setTimeout 當delay 大於2147483647 或小於1 時,則delay 將會被設為1, 非整數的delay 會被截斷為整數

#奇怪的執行順序

##看一個範例,用幾種方法分別非同步列印一個數字

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


同步& 非同步

第五行是同步執行,其它都是非同步的

setImmediate(console.log, 1);
setTimeout(console.log, 1, 2);
Promise.resolve(3).then(console.log);
process.nextTick(console.log, 4);
/****************** 同步任务和异步任务的分割线 ********************/
console.log(5);
所以先列印5,這個很好理解,剩下的都是非同步操作,Node.js 按照什麼順序執行呢?


event loop

Node.js 啟動後會初始化事件輪詢,過程中可能會處理非同步呼叫、定時器調度和process.nextTick(),然後開始處理event loop 。官網中有這樣一張圖用來介紹 event loop 操作順序

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

event loop 的每個階段都有一個任務隊列,當event loop 進入給定的階段時,將執行該階段的任務隊列,直到隊列清空或執行的回調達到系統上限後,才會轉入下一個階段,當所有階段被順序執行一次後,稱event loop 完成了一個tick

異步操作都被放到了下在一個event loop tick 中,process.nextTick 在進入下一次event loop tick 之前執行,所以肯定在其它非同步操作之前

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

各個階段主要任務

  • #timers:執行setTimeout、setInterval 回呼

  • #pending callbacks:執行I/O(檔案、網路等) 回呼

  • idle, prepare:僅供系統內部呼叫

  • #poll:取得新的I/O 事件,執行相關回調,在適當條件下把阻塞node

  • check:setImmediate 回呼在此階段執行

  • close callbacks:執行socket 等的close 事件回呼

#日常開發中絕大部分非同步任務都是在timers、poll、check 階段處理的


timers


Node.js 會在timers 階段檢查是否有過期的timer,如果存在則把回調放到timer 佇列中等待執行,Node.js 使用單線程,受限於主執行緒空閒情況和機器其它進程影響,並不能保證timer 依照精確時間執行

定時器主要有兩種

  • Immediate

  • Timeout

Immediate 類型的計時器回調會在

check 階段被調用,Timeout 計時器會在設定的時間過期後儘快的調用回調,但

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

setImmediate(() => {
  console.log('immediate');
});
多次執行會發現列印的順序不一樣


##poll

poll 階段主要有兩個任務

##計算應該要阻塞和輪詢I/O 的時間
  • 然後,處理poll 佇列裡的事件
  • 當event loop進入poll 階段且沒有被調度的計時器時

如果poll 隊列不是空的,event loop 將循環訪問回調隊列並同步執行,直到隊列已用盡或達到了系統或達到最大回呼數

    如果poll 佇列是空的
  • 如果有 setImmediate() 任務,event loop 會在結束
  • poll
      階段後進入 
    • check 階段如果沒有 setImmediate()任務,event loop 阻塞在poll
    • 階段等待回呼被加入到佇列中,然後立即執行
    一旦
  • poll
隊列為空,event loop 將檢查timer 隊列是否為空,如果非空則進入下一輪event loop

上面提到如果在不同的I/O 裡,不能確定 setTimeout 和setImmediate 的執行順序,但如果setTimeout 和setImmediate 在一個I/O 回調裡,肯定是setImmediate 先執行,因為在poll 階段檢查到有 setImmediate() 任務,event loop 直接進入check 階段執行回調setImmediate event loop 直接進入check 階段執行回調setImmediate

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

check
在該階段執行setImmediate 回呼

为什么 Promise.then 比 setTimeout 早一些

前端同学肯定都听说过 micoTask 和 macroTask,Promise.then 属于 microTask,在浏览器环境下 microTask 任务会在每个 macroTask 执行最末端调用

在 Node.js 环境下 microTask 会在每个阶段完成之间调用,也就是每个阶段执行最后都会执行一下 microTask 队列

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);
/****************** 同步任务和异步任务的分割线 ********************/
console.log(5);

setImmediate VS process.nextTick

setImmediate 听起来是立即执行,process.nextTick 听起来是下一个时钟执行,为什么效果是反过来的?这就要从那段不堪回首的历史讲起

最开始的时候只有 process.nextTick 方法,没有 setImmediate 方法,通过上面的分析可以看出来任何时候调用 process.nextTick(),nextTick 会在 event loop 之前执行,直到 nextTick 队列被清空才会进入到下一 event loop,如果出现 process.nextTick 的递归调用程序没有被正确结束,那么 IO 的回调将没有机会被执行

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();

执行程序将返回

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

于是乎需要一个不这么 bug 的调用,setImmediate 方法出现了,比较令人费解的是在 process.nextTick 起错名字的情况下,setImmediate 也用了一个错误的名字以示区分。。。

那么是不是编程中应该杜绝使用 process.nextTick 呢?官方推荐大部分时候应该使用 setImmediate,同时对 process.nextTick 的最大调用堆栈做了限制,但 process.nextTick 的调用机制确实也能为我们解决一些棘手的问题

  • 允许用户在 even tloop 开始之前 处理异常、执行清理任务

  • 允许回调在调用栈 unwind 之后,下次 event loop 开始之前执行

一个类继承了 EventEmitter,而且想在实例化的时候触发一个事件

const EventEmitter = require(&#39;events&#39;);
const util = require(&#39;util&#39;);

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

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

在构造函数执行 this.emit('event') 会导致事件触发比事件回调函数绑定早,使用 process.nextTick 可以轻松实现预期效果

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中的定時器的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:cnblogs.com。如有侵權,請聯絡admin@php.cn刪除