Home  >  Article  >  Web Front-end  >  Understanding timers in Node.js

Understanding timers in Node.js

青灯夜游
青灯夜游forward
2020-12-03 18:11:133230browse

Understanding timers in Node.js

Related recommendations: "node js tutorial"

timer is used to schedule functions to be called at a certain point in the future, Node The timer function in .js implements an API similar to the timer API provided by the web browser, but uses an event loop to implement it. There are four related methods in Node.js

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

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

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

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

First two The meaning is consistent with that on the web. The last two are unique to Node.js. The effect seems to be setTimeout(callback, 0). It is most commonly used in Node.js programming

Node.js does not The exact time the callbacks are fired is guaranteed, nor is their order guaranteed; the callbacks will be called as close as possible to the specified time. setTimeout When delay is greater than 2147483647 or less than 1, delay will be set to 1, and non-integer delays will be truncated to integers

Strange execution order

Look at an example , use several methods to print a number asynchronously

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

will print 5 4 3 2 1 or 5 4 3 1 2

Synchronous & asynchronous

The fifth line It is executed synchronously, and the others are all asynchronous

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

So print 5 first. This is easy to understand. The rest are asynchronous operations. In what order does Node.js execute?

event loop

Node.js will initialize event polling after startup. In the process, it may process asynchronous calls, timer scheduling and process.nextTick(), and then start processing event loop . There is such a picture on the official website to introduce the event loop operation sequence

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

Each stage of the event loop has a task queue. When the event loop enters a given stage, the task queue of that stage will be executed. , will not move to the next stage until the queue is cleared or the executed callback reaches the system upper limit. When all stages are executed in sequence, the event loop is said to have completed a tick

Asynchronous operations are placed next In an event loop tick, process.nextTick is executed before entering the next event loop tick, so it must be before other asynchronous operations

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

Main tasks in each stage

  • timers: Execute setTimeout, setInterval callbacks

  • pending callbacks: Execute I/O (file, network, etc.) callbacks

  • idle, prepare: Only called internally by the system

  • poll: Get new I/O events and execute Related callbacks, block the node under appropriate conditions

  • check: The setImmediate callback is executed at this stage

  • close callbacks: Execute the close event callback of socket etc.

Most asynchronous tasks in daily development are processed in the timers, poll, and check stages

timers

Node.js will check whether there is an expired timer in the timers phase. If it exists, the callback will be placed in the timer queue to wait for execution. Node.js uses a single thread and is limited. Due to the idle situation of the main thread and the influence of other processes on the machine, there is no guarantee that the timer will be executed according to the precise time.
There are two main types of timers

  • Immediate

  • Timeout

Immediate type timer callback will be called in the check phase. The Timeout timer will call the callback as soon as possible after the set time expires. , but

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

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

If executed multiple times, you will find that the order of printing is different

poll

The poll stage mainly has two tasks

  • Calculate the time that should block and poll I/O

  • Then, process the events in the poll queue

When event loop When entering the poll phase and there is no scheduled timer

  • If the poll queue is not empty, the event loop will cycle through the callback queue and execute synchronously until the queue is exhausted or reaches the system or reaches the maximum Number of callbacks
  • If the poll queue is empty
    • If there is a setImmediate() task, the event loop will enter the check phase after ending the poll phase
    • If there is no setImmediate() task, the event loop blocks in the poll stage waiting for the callback to be added to the queue, and then executes it immediately

Oncepoll the queue is empty, the event loop will check whether the timer queue is empty. If it is not empty, it will enter the next round of event loop

As mentioned above, if in different I/O , the execution order of setTimeout and setImmediate cannot be determined, but if setTimeout and setImmediate are in an I/O callback, setImmediate must be executed first, because the setImmediate() task is detected in the poll phase, and the event loop directly enters the check phase to execute the setImmediate callback

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

check

Execute setImmediate callback at this stage

为什么 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!');
});

更多编程相关知识,请访问:编程教学!!

The above is the detailed content of Understanding timers in Node.js. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:cnblogs.com. If there is any infringement, please contact admin@php.cn delete