Home>Article>Web Front-end> 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
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
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?
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
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 thecheckphase. 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
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
Oncepollthe 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'); }); });
Execute setImmediate callback at this stage
前端同学肯定都听说过 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 听起来是立即执行,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('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!'); });
在构造函数执行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!