What is Event Cycle
As we all know, JavaScript is single-threaded, and Nodejs can implement non-blocking I/O operations, because Event Loop exists.
Event Loop has the following stages, a rectangle represents a stage, as follows:
┌───────────────────────┐ ┌─>│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ I/O callbacks │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └──────────┬────────────┘ │ data, etc. │ │ ┌──────────┴────────────┐ └───────────────┘ │ │ check │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ └──┤ close callbacks │ └───────────────────────┘
Each stage maintains a FIFO queue structure. When the event loop enters a certain stage, some specific operations of that stage are performed, and then the callback functions in the queue are executed sequentially. When the callback functions in the queue are executed or the number of callback functions that have been executed reaches a certain maximum, the next stage will be entered.
timers
At the beginning of the event loop, the callback functions of setTimeout and setInterval are executed.
When the timer's time is up, the callback function of the timer is put into the queue and executed sequentially.
If you have four timers a, b and c, the time intervals are 10 ms, 20 ms and 30 ms, respectively. When we enter the timer phase of the event cycle, the time passes by 25 ms, then the callbacks of timers A and b will be executed and the next phase will be entered after execution.
I/O callbacks
Execute callback functions other than setTimeout, setInterval, setImmediate, and close callbacks.
idle, prepare
Do some internal operations.
poll
This should be the most important stage in the event cycle.
If the queue at this stage is not empty, the callbacks in the queue will be executed sequentially; if the queue is empty, the setImmediate function will also be called, then the check ing stage will be entered. If the queue is empty and there is no setImmediate function call, the event loop waits and executes as soon as a callback function is added to the queue.
check
setImmediate callbacks are executed at this stage.
close callbacks
Callbacks such as socket.on('close',...) are executed at this stage.
setTimout vs setImmediate
// timeout_vs_immediate_1.js setTimeout(function timeout() { console.log('timeout'); }, 0); setImmediate(function immediate() { console.log('immediate'); });
According to the previous statement, the event loop enters the timer stage first, and the setTimeout callback is executed until the check ing stage, before the setImmediate callback is executed. So some people think that the output of the above code should be:
$ node timeout_vs_immediate_1.js timeout immediate
But the results here are uncertain. The performance of the follow-up process is often related here, and the setTimeout interval here, though zero, will actually be 1. So when the starter enters the event loop and the time has not passed 1ms, the queue in the timer phase is empty and no callbacks are executed. There is also a call to the setImmediate function, so the callback to the setImmediate will be called when it comes to the check stage. If the event loop has consumed 1 ms when it enters the timer phase, then the setTimeout callback will be executed at this time, before entering the check phase, and then the setImmediate callback will be executed.
Therefore, the following two kinds of output may occur.
$ node timeout_vs_immediate_1.js timeout immediate $ node timeout_vs_immediate_1.js immediate timeout
Suppose the above code is placed in an I/0 loop, such as
// timeout_vs_immediate_2.js const fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); }); });
So the result is definite. The output is as follows.
$ node timeout_vs_immediate_2.js immediate timeout
process.nextTick()
process.nextTick() is not part of the event loop, but is also an asynchronous API.
After the current operation is completed, if there is a call to process.nextTick(), then the callback in process.nextTick() will be executed. If there is process.nextTick() in the callback, then the callback of process.nextTick() in the callback will also be executed. So procee.nextTick() may block the event cycle to the next stage.
// process_nexttick_1.js let i = 0; function foo() { i += 1; if (i > 3) return; console.log('foo func'); setTimeout(() => { console.log('timeout'); }, 0); process.nextTick(foo); } setTimeout(foo, 5);
According to the previous statement, the above output results are as follows:
$ node process_nexttick_1.js foo func foo func foo func timeout timeout timeout
You may wonder whether process.nextTick() is executed after the queues at some stage of the event cycle are empty or after a callback in the queue is executed. Think about the output of the following code?
// process_nexttick_2.js let i = 0; function foo() { i += 1; if (i > 2) return; console.log('foo func'); setTimeout(() => { console.log('timeout'); }, 0); process.nextTick(foo); } setTimeout(foo, 2); setTimeout(() => { console.log('another timeout'); }, 2);
Execute and see the results.
// node version: v11.12.0 $ node process_nexttick_2.js foo func foo func another timeout timeout timeout
As shown above, process.nextTick() is executed after a callback in the queue is completed.
As you can see above, there is a comment node version: v11.12.0. That is, the node version running this code is 11.12.0. If your node version is lower than this, such as 7.10.1, you may get different results.
// node version: v7.10.0 $ node process_nexttick_2.js foo func another timeout foo func timeout timeout
Different versions behave differently. I think the new version has been updated and adjusted.
process.nextTick() vs Promise
process.nextTick() corresponds to nextTickQueue, and Promise corresponds to microTaskQueue.
Neither of them belongs to a part of the event cycle, but when they are executed after the current operation, how about the execution of the two?
// process_nexttick_vs_promise.js let i = 0; function foo() { i += 1; if (i > 2) return; console.log('foo func'); setTimeout(() => { console.log('timeout'); }, 0); Promise.resolve().then(foo); } setTimeout(foo, 0); Promise.resolve().then(() => { console.log('promise'); }); process.nextTick(() => { console.log('nexttick'); });
Running the code, the results are as follows:
$ node process_nexttick_vs_promise.js nexttick promise foo func foo func timeout timeout
If you understand the output results above, you can also understand the event loop in Nodejs.
Reference material
- The Node.js Event Loop, Timers, and process.nextTick()
- Node.js event loop workflow & lifecycle in low level
discuss
Welcome to discuss with us. If you have any good points, please correct them.