Home >Web Front-end >JS Tutorial >An in-depth analysis of events and event loops in nodejs
This article will explain to you the events in nodejs, and discuss the differences between setTimeout, setImmediate and process.nextTick. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to everyone.
Although nodejs is single-threaded, nodejs can operate Delegate it to the system kernel, which processes these tasks in the background. When the task is completed, it notifies nodejs, thus triggering the callback method in nodejs.
These callbacks will be added to the round robin queue and eventually executed.
Through this event loop design, nodejs can finally achieve non-blocking IO. [Related recommendations: "nodejs Tutorial"]
The event loop in nodejs is divided into phases. The following figure lists the execution order of each phase:
Each phase maintains a callback queue, which is a FIFO queue.
When entering a phase, it will first execute the tasks of the phase, and then execute the callback tasks belonging to the phase.
When all tasks in this callback queue have been executed or the maximum number of callback executions has been reached, the next phase will be entered.
Note that the specific implementations of Windows and Linux are slightly different. Here we only focus on the most important phases.
Question: During the execution of phase, why should we limit the maximum number of callback executions?
Answer: In extreme cases, a certain phase may need to execute a large number of callbacks. If it takes too much time to execute these callbacks, it will block the operation of nodejs, so we set the number of callback executions. Limit to avoid long blocks of nodejs.
In the above picture, we have listed 6 phases, and we will explain them one by one next.
timers
The Chinese meaning of timers is timer, which means to execute a certain callback function at a given time or interval.
There are two common timers functions: setTimeout and setInterval.
Generally speaking, these callback functions will be executed as much as possible after expiration, but will be affected by the execution of other callbacks. Let's look at an example:
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 completesomeAsyncOperation(() => { const startCallback = Date.now(); // do something that will take 10ms... while (Date.now() - startCallback < 10) { // do nothing }});
In the above example, we called someAsyncOperation. This function first goes back to execute the readFile method. Assume that this method takes 95ms. Then execute the callback function of readFile. This callback will execute for 10ms. Finally, go back and execute the callback in setTimeout.
So in the above example, although setTimeout is specified to run after 100ms, it actually has to wait for 95 10 = 105 ms before it is actually executed.
pending callbacks
This phase will perform some system callback operations. For example, when making a TCP connection, the TCP socket receives the ECONNREFUSED signal. In some liunx This error will be reported in the operating system, and the callback of this system will be run in pending callbacks.
Or the I/O callback operation that needs to be performed in the next event loop.
idle, prepare
idle, prepare is the phase used internally, so I won’t introduce it in detail here.
poll polling
poll will detect new I/O events and execute I/O-related callbacks. Note that the callbacks here refer to everything except Turn off almost all callback events except callback, timers, and setImmediate.
poll mainly handles two things: polling I/O, calculating block time, and then processing events in the poll queue.
If the poll queue is not empty, the event loop will traverse the callbacks in the queue and then execute them synchronously one by one until the queue is consumed or the limit on the number of callbacks is reached.
Because the callbacks in the queue are executed synchronously one by one, blocking may occur.
If the poll queue is empty and setImmediate is called in the code, it will immediately jump to the next check phase, and then execute the callback in setImmediate. If setImmediate is not called, it will continue to wait for the new callback to be added to the queue and executed.
check
Mainly used to execute the callback of setImmediate.
setImmediate can be seen as a unique timer running in a separate phase, and the underlying libuv API is used to plan callbacks.
Generally speaking, if there is a callback called setImmediate in the poll phase, the poll phase will be ended immediately when the poll queue is empty, and the check phase will be entered to execute the corresponding callback method.
close callbacks
The last phase is to handle the callbacks in the close event. For example, if a socket is suddenly closed, a close event will be triggered and the related callback will be called.
setTimeout和setImmediate有什么不同呢?
从上图的phase阶段可以看出,setTimeout中的callback是在timer phase中执行的,而setImmediate是在check阶段执行的。
从语义上讲,setTimeout指的是,在给定的时间之后运行某个callback。而setImmediate是在执行完当前loop中的 I/O操作之后,立马执行。
那么这两个方法的执行顺序上有什么区别呢?
下面我们举两个例子,第一个例子中两个方法都是在主模块中运行:
setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); });
这样运行两个方法的执行顺序是不确定,因为可能受到其他执行程序的影响。
第二个例子是在I/O模块中运行这两个方法:
const fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); }); });
你会发现,在I/O模块中,setImmediate一定会在setTimeout之前执行。
两者的共同点
setTimeout和setImmediate两者都有一个返回值,我们可以通过这个返回值,来对timer进行clear操作:
const timeoutObj = setTimeout(() => { console.log('timeout beyond time'); }, 1500); const immediateObj = setImmediate(() => { console.log('immediately executing immediate'); }); const intervalObj = setInterval(() => { console.log('interviewing the interval'); }, 500); clearTimeout(timeoutObj); clearImmediate(immediateObj); clearInterval(intervalObj);
clear操作也可以clear intervalObj。
unref 和 ref
setTimeout和setInterval返回的对象都是Timeout对象。
如果这个timeout对象是最后要执行的timeout对象,那么可以使用unref方法来取消其执行,取消执行完毕,可以使用ref来恢复它的执行。
const timerObj = setTimeout(() => { console.log('will i run?'); }); timerObj.unref(); setImmediate(() => { timerObj.ref(); });
注意,如果有多个timeout对象,只有最后一个timeout对象的unref方法才会生效。
process.nextTick也是一种异步API,但是它和timer是不同的。
如果我们在一个phase中调用process.nextTick,那么nextTick中的callback会在这个phase完成,进入event loop的下一个phase之前完成。
这样做就会有一个问题,如果我们在process.nextTick中进行递归调用的话,这个phase将会被阻塞,影响event loop的正常执行。
那么,为什么我们还会有process.nextTick呢?
考虑下面的一个例子:
let bar; function someAsyncApiCall(callback) { callback(); } someAsyncApiCall(() => { console.log('bar', bar); // undefined }); bar = 1;
上面的例子中,我们定义了一个someAsyncApiCall方法,里面执行了传入的callback函数。
这个callback函数想要输出bar的值,但是bar的值是在someAsyncApiCall方法之后被赋值的。
这个例子最终会导致输出的bar值是undefined。
我们的本意是想让用户程序执行完毕之后,再调用callback,那么我们可以使用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', () => {});
上面的例子是最简单的nodejs创建web服务。
上面的例子有什么问题呢?listen(8000) 方法将会立马绑定8000端口。但是这个时候,server的listening事件绑定代码还没有执行。
这里实际上就用到了process.nextTick技术,从而不管我们在什么地方绑定listening事件,都可以监听到listen事件。
process.nextTick 和 setImmediate 的区别
process.nextTick 是立马在当前phase执行callback,而setImmediate是在check阶段执行callback。
所以process.nextTick要比setImmediate的执行顺序优先。
实际上,process.nextTick和setImmediate的语义应该进行互换。因为process.nextTick表示的才是immediate,而setImmediate表示的是next tick。
本文作者:flydean程序那些事
本文链接:http://www.flydean.com/nodejs-event-more/
本文来源:flydean的博客
更多编程相关知识,请访问:编程视频!!
The above is the detailed content of An in-depth analysis of events and event loops in nodejs. For more information, please follow other related articles on the PHP Chinese website!