Such an easy-to-understand Node event loop is over

Introduction

Although jsit can be executed in the browser and in the browser node, their event loop mechanisms are not the same. And there is a big difference.

If you are not familiar with the browser event loop mechanism, you can read what the author wrote earlier in 2022. You don’t know the js execution context and event loop mechanism . Today we only talk about Nodethe event loop mechanism in China.

EventLoop Mechanism Overview

Before talking about Nodethe event loop mechanism, let's discuss two issues

Why learn the event loop mechanism?

Learning the event loop allows developers to understand JavaScriptwhat the operating mechanism is like.

What does the event loop mechanism do?

The event loop mechanism is used to manage when the callback function of the asynchronous API returns to the main thread for execution .

Node.js uses an asynchronous IO model. The synchronous API is executed in the main thread, the asynchronous API is executed in the thread maintained by the underlying C++, and the callback function of the asynchronous API is also executed in the main thread.

When the Javascript application is running, when can the callback functions of many asynchronous APIs be called back to the main thread? This is what the event loop mechanism does, managing when the callback function of the asynchronous API returns to the main thread for execution.

The six stages of EventLoop

The event loop in Nodeis divided into six phases.

Each stage in the event loop has a queue to store the callback functions to be executed, and the event loop mechanism will execute them in a first-in-first-out manner until the queue is empty.

These six stages all store asynchronous callback functions, so the main thread synchronization code is executed first, and these six stages are polled after the synchronization code is executed.

Next, let's take a closer look at what is stored in these six stages

Timers

Timers: The callback function used to store the timer (setlnterval, setTimeout).

Pendingcallbacks

Pendingcallbacks: Execute the callback function related to the operating system, such as the callback function for listening to the port operation when starting the server-side application is called here.

idle,prepare

idle,prepare: Used internally by the system. (We programmers don't care about this)

Poll

Poll: Store the callback function queue for I/O operations, such as the callback function for file read and write operations.

Special attention needs to be paid at this stage, if there are callback functions in the event queue, they will be executed until the queue is emptied
, otherwise the event loop will stay in this stage for a while waiting for new callback functions to enter.

But this wait is not certain, but depends on the following two conditions:

  • If there is a call function to be executed in the setlmmediate queue (check phase). This situation will not wait.
  • There is a callback function to be executed in the timers queue, and it will not wait in this case. The event loop will move to the check phase, then to the Closing callbacks phase, and finally from the timers phase to the next cycle.

Check

Check: Store the callback function of setlmediate.

Closingcallbacks

Closingcallbacks: Execute the callback related to the close event, such as the callback function for closing the database connection, etc.

Macrotasks and Microtasks

Just like in the browser js, nodethe asynchronous code in is also divided into macrotasks and microtasks, but the order of execution between them is different.

Let's take a look at Nodewhat macro tasks and micro tasks are in

macro task

  1. setlnterval
  2. setimeout
  3. setlmmediate
  4. I/O

micro task

  1. Promise.then
  2. Promise.catch
  3. Promise.finally
  4. process.nextTick

In node, what is the execution order of microtasks and macrotasks?

Execution order of microtasks and macrotasks

In node, the callback function of the microtask is placed in the microtask queue, and the callback function of the macrotask is placed in the macrotask queue.

Microtasks have higher priority than macrotasks. When there is an executable callback function in the microtask event queue, the event loop will pause and enter the next stage of the event loop after executing the callback function of the current stage, and will immediately enter the event queue of the microtask to start executing the callback function. After the execution of the callback function in the microtask queue is completed, the event loop will enter the next segment and start executing the callback function.

There is one more point we need to pay special attention to for microtasks. That is, although nextTickit belongs to microtasks, its priority is higher than other microtasks. When executing microtasks, other nextlickmicrotasks will not be executed until all callback functions in it are executed.

In general, when the main thread synchronization code is executed, the microtask will be cleared first (if the microtask continues to generate microtasks, it will be cleared again), and then go to the next event cycle stage. And the execution of microtasks is interspersed among the six stages of the event loop, that is, before each event loop enters the next stage, it will judge whether the microtask queue is empty, and it will enter the next stage if it is empty, otherwise the microtask will be cleared first queue.

Let's use code practice to verify what we said earlier.

code example

Execute synchronously before asynchronously

After Nodethe application starts, it does not immediately enter the event loop, but executes the synchronous code first, starting from top to bottom. The synchronous API is executed immediately, and the asynchronous API is handed over to the thread maintained by C++ for execution. The callback function of the asynchronous API is registered. to the corresponding event queue. The event loop will not be entered until all synchronous code execution is complete.

console.log("start");

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

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

console.log("end");

Let's see the execution result

image.png

It can be seen that the synchronous code is executed first, and then the event loop is executed to execute the asynchronous code, and timerstwo setTimeoutcallbacks are executed in the stage.

Will setTimeout be executed before setImmediate?

We know setTimeoutthat it is carried out in timersstages, setImmediateit is carried out in checkstages. And the event loop is timersstarted from phase. So it will be executed first setTimeoutand then executed setImmediate.

Is the above analysis correct?

Let's look at an example

console.log("start");

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

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

const sleep = (delay) => {
    
    
  const startTime = +new Date();
  while (+new Date() - startTime < delay) {
    
    
    continue;
  }
};

sleep(2000);
console.log("end");

Execute the above code, the output is as follows

image.png

Execute first setTimeoutand then executesetImmediate

Next, let's modify the above code, remove the delayer, and see what will be output

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

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

We run it seven times and we can see that two of them are run firstsetImmediate

image.png

What's going on? Isn't it the first timersstage and then checkthe stage? How can it change?

In fact, it depends on whether the asynchronous callback is fully ready when entering the event loop. For the first example, because there is a delay of 2000 milliseconds, when entering the event loop, setTimeoutthe callback must be ready. So the order of execution will not change. But for this example, because the main thread has no synchronous code to execute, it enters the event loop at the beginning, but when entering the event loop, the setTimeoutcallback is not necessarily fully prepared, so there will be a first checkstage to execute setImmediatethe callback function , and then to the next event loop timersstage to execute setTimeoutthe callback.

Under what circumstances setImmediatewill the callback function be prioritized over the callback for the same delay time setTimeout?

In fact, it is very simple, as long as the two are placed in any stage of timersthe stage and the stage checkbetween the stages . Pendingcallbacks、idle,prepare、pollBecause these stages are completed and executed, they will definitely come first checkand then come timersto the stage.

Let's take pollthe stage as an example and write these two in the IO operation.

const fs = require("fs");

fs.readFile("./fstest.js", "utf8", (err, data) => {
    
    
  setTimeout(() => {
    
    
    console.log("setTimeout");
  });

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

Let's also execute it seven times, as you can see, each time it is setImmediateexecuted first.

image.png

So in general, the same delay time, setTimeoutnot 100% ahead of setImmediateexecution.

Microtasks before macrotasks

After the main thread synchronization code is executed, the microtask will be executed first and then the macrotask will be executed.

Let's look at the following example

console.log("start");

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

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

Promise.resolve().then(() => {
    
    
  console.log("Promise.resolve");
});

console.log("end");

Let's run it to see the result, we can see that it executes the microtask first and then executes the macrotask

image.png

nextTick is superior to other microtasks

nextTickPriority is highest among microtasks .

Let's look at the following example

console.log("start");

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

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

Promise.resolve().then(() => {
    
    
  console.log("Promise.resolve");
});

process.nextTick(() => {
    
    
  console.log("process.nextTick");
});

console.log("end");

When we run the above code, we can see that even if it nextTickis defined resolvelater, it is executed first.

image.png

Microtasks are interspersed between stages

How to understand this interlude? In fact, the microtask queue will be cleared after each of the six stages of the event loop is executed.

Let's look at an example, we have established timers、check、pollthree stages, and each stage generates microtasks.

// timers阶段
setTimeout(() => {
    
    
  console.log("setTimeout");

  Promise.resolve().then(() => {
    
    
    console.log("setTimeout Promise.resolve");
  });
});

// check阶段
setImmediate(() => {
    
    
  console.log("setImmediate");
  Promise.resolve().then(() => {
    
    
    console.log("setImmediate Promise.resolve");
  });
});

// 微任务
Promise.resolve().then(() => {
    
    
  console.log("Promise.resolve");
});

// 微任务
process.nextTick(() => {
    
    
  console.log("process.nextTick");
  Promise.resolve().then(() => {
    
    
    console.log("nextTick Promise.resolve");
  });
});

Let's execute the above code

image.png

As you can see, the microtasks are executed first, and then the macrotasks are executed. first process.nextTick -> Promise.resolve. And if the microtask continues to generate microtasks, it will be cleared again, so it will be output again nextTick Promise.resolve.

The next timerstage is output setTimeout, and a microtask is generated, and the microtask queue needs to be cleared before entering the next stage, so continue to output setTimeout Promise.resolve.

The next checkstage is output setImmediate, and a microtask is generated, and the microtask queue needs to be cleared before entering the next stage, so continue to output setImmediate Promise.resolve.

This also confirms that microtasks will be interspersed between various stages.

image.png

Summarize

So Nodeyou only need to memorize the following points for the event loop in

  1. When the main thread synchronization code is executed, it will enter the event loop
  2. The event cycle is divided into six phases, and which callbacks are included in each phase need to be remembered clearly.
  3. In the event loop, microtasks are executed first and then macrotasks are executed.
  4. Microtasks will be executed between these six stages, and the current microtask queue will be cleared before entering the next stage.
  5. The microtask process.nextTickhas the highest priority and will be executed first.

series of articles

Introduction to Node.js What is Node.js

The path module for getting started with Node.js

The fs module for getting started with Node.js

The url module and querystring module for getting started with Node.js

http module and dns module for getting started with Node.js

Node.js entry process module, child_process module, cluster module

I heard you don't know how to use Express yet?

I heard you don't know how to use Koa yet?

postscript

This article is the author's personal study notes, if there is any fallacy, please let me know, thank you very much! If this article is helpful to you, please give it a follow and a like~, your support is the motivation for the author to keep updating.

Guess you like

Origin blog.csdn.net/weixin_38664300/article/details/131008338