The architecture of NodeJS is shown in the following figure. JS code is executed through the V8 engine, and some operations are done by reading and writing file system, network and so on through the middle layer libuv.

NodeJS provides blocking and non-blocking calls, such as FS module to read files, you can use ReadFile (asynchronous) or ReadFileSync (synchronous) according to the need.

With synchronous programming, subsequent code execution will have to wait until the end of the current execution, and the program will be “blocked.” It is also possible to use asynchronous programming. Subsequent code execution does not have to wait for the completion of this execution, so it does not “block” the execution of the program, but it also has the problem that non-blocking calls require constant polling for the results of the asynchronous call.

I/O in libuv uses “non-blocking call”, but if it keeps polling for results, it will have a certain performance impact on the system. In order to reduce this impact, libuv puts the process of constantly polling into the “thread pool”. When the result is polled, libuv puts the process of constantly polling into the “thread pool”. The corresponding callback and the result are placed on a queue in the Event Loop, which then proceeds to perform the next step, using JavaScript to execute the callback.

The event loop in NodeJS is a little more complex than the JavaScript event loop, and a loop is divided into the following stages

* timer (timer): Pending callbacks: Some system operations (e.g., TCP error type) perform callbacks * Idle, prepare: Used internally only. * Poll: to retrieve new I/O events; Perform I/ O-related callbacks (in almost all cases, except for closed callbacks, those scheduled by timers and setMediate ()) * Check (Check) : The setMediate () callback is executed here. * Close callbacks: Some closed callbacks, such as socket.on('close'...) .

Similar to JavaScript, there are micro-tasks and macro-tasks in NodeJS, and the contents executed in the two tasks are also somewhat similar

Microtasks: Callbacks to Promise's Then function, QueueMicroTask, Process. NextTick macro tasks: SetTimeout, SetInterval, IO events, SetMediate, Close events

The order of execution is the same as in JavaScript, first the main thread task is executed, then the microtask is executed, and then the macro task is executed. The specific order of execution is as follows.

Next Tick Queue: process.nextTick Other Tick Queue: Promise's Then function QueueMicroTask macro task queue timer queue: SetTimeout, SetInterval Poll Queue: IO Event Check Queue: SetMediate Close Queue: Close Event

Now that you know the execution order of the event loop in NodeJS, let’s take a look at the following interview question

async function async1() { console.log('async1 start') await async2() console.log('async1 end') } async function async2()  { console.log('async2') } console.log('script start') setTimeout(function () { console.log('setTimeout0') }, 0) setTimeout(function () { console.log('setTimeout2') }, 300) setImmediate(() => console.log('setImmediate')); process.nextTick(() => console.log('nextTick1')); async1(); process.nextTick(() => console.log('nextTick2')); new Promise(function (resolve) { console.log('promise1') resolve(); console.log('promise2') }).then(function () { console.log('promise3') }) console.log('script end')

The async1 and async2 functions are declared first. Only calls are put on the call stack, so they are not executed at this time.

Continue executing. “setTimeout0” will be delayed for 300ms. “setTimeout2” will not be placed in the timer queue. Then put “setMediate” into the check queue and “nextTick1” into the next tick queue.

When async1 is executed, output “async1 start”. When async2 is executed in async1, output “async2” and put “async1 end” into the other queue. Then async1 is executed. (promise1) Put promise3 in the next tick-queue (async1 end) and promise1 in the next tick-queue (async1 end). Put promise3 in the other queue (async1 end). Output “promise2”, and finally output “script end”. At this point, all of the main thread is finished.

In this microtask queue, the contents in the Next Tick queue are first output “nectTick1” and “nectTick2” in turn, and then the contents in the Other queue are output “async1 end” and “promise3” in turn.

Finally, execute the task in the macro task queue, first execute the timer queue, then print “setTimeout0”, there is no IO event (poll), then proceed to check quue, then print the setMediate, Without closing the callbasks stage, the first event cycle ends and the second and third event cycles come to an end. At this time, 300ms later, “setTimeout2” is added to the timer queue in the macro task queue. There is no other queue in the event cycle. Print “setTimeout2” directly and the event loop ends.

The simple diagram is as follows

Here’s another interview question

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

setImmediate(() => {
  console.log("setImmediate");
});

According to the execution order of the tasks in the macro task queue, setTimeout is part of the Timer Queue and setMediate is part of the Check Queue. Theoretically, setTimeout would be output first, but what would happen in practice? Let’s look at the following output

The reason why this is so is that sometimes setTimeout outputs first, and sometimes setMediate outputs first. The reason is that setTimeout’s callback is delayed by 0 milliseconds, but setTimeout takes longer to prepare than the event loop’s start time. When the Event Loop starts its first loop, the setTimeout has not yet been placed on the Timer Queue, so the Event Loop executes the SetMediate in the Check Queue and waits for the second loop, SetTimeout is only present in the Timer Queue, and then the SetMediate will output first.

The event loop in NodeJS is partially the same as the event loop in JavaScript. If you are not familiar with the event loop in JavaScript, take a look at this article for a detailed explanation of the event loop in JavaScript and interview questions