Home  >  Article  >  Web Front-end  >  In-depth understanding of Node event loop (EventLoop) mechanism

In-depth understanding of Node event loop (EventLoop) mechanism

青灯夜游
青灯夜游forward
2023-03-16 20:11:321885browse

The main thread reads events from the "task queue". This process is cyclic, so the entire running mechanism is also called Event Loop. The following article will help you master the eventloop in Node.js. I hope it will be helpful to you!

In-depth understanding of Node event loop (EventLoop) mechanism

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

Overview of the EventLoop mechanism

Before talking about the Node event loop mechanism, let us first discuss two issues

Why should we learn the event loop mechanism?

Learning the event loop can allow developers to understand how JavaScript operates.

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 will also be executed in the main thread. [Recommended related tutorials: nodejs video tutorial, Programming teaching]

When a Javascript application is running, when can the callback functions of many asynchronous APIs return to the main thread? What about calls? 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 Node is divided into six stages.

There is a queue at each stage in the event loop to store the callback functions to be executed. The event loop mechanism will execute them in a first-in, first-out manner until the queue is null.

These six stages all store asynchronous callback functions, so it is still necessary to execute the main thread synchronization code first, and then poll these six stages after the synchronization code is executed.

Next, let’s take a detailed look at what is stored in these six stages

Timers

Timers : Callback function (setlnterval, setTimeout) used to store timers.

Pendingcallbacks

Pendingcallbacks: Execute callback functions related to the operating system, such as the callback function that monitors port operations when starting a server-side application. Called here.

idle, prepare

idle, prepare: used internally by the system. (We programmers don’t need to worry about this)

Poll

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

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 cleared. , otherwise the event loop will stay at this stage for a while to wait for a new callback function to enter.

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

  • If there is something to be executed in the setlmmediate queue (check phase) adjustment function. In this case there will be no waiting.
  • There is a callback function to be executed in the timers queue, and there will be no waiting in this case. The event loop will move to the check phase, then to the Closingcallbacks phase, and finally from the timers phase to the next loop.

Check

Check: Stores the callback function of setlmmediate.

Closingcallbacks

Closingcallbacks: Execute callbacks related to closing events, such as callback functions for closing database connections, etc.

Macro tasks and micro tasks

Like js in the browser, the asynchronous code in node is also divided into They are macrotasks and microtasks, but the execution order between them is different.

Let’s take a look at the macro tasks and micro tasks in Node

Macro tasks

  • setlnterval

  • setimeout

  • setlmmediate

  • I/O

Microtasks

  • ##Promise.then

  • Promise.catch

  • Promise.finally

  • process.nextTick

In

node, for microtasks and macros What is the order of execution of tasks?

Execution sequence 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 macro task 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 microtask's event queue to start executing the callback function. When the callback function in the microtask queue is executed, the event loop will enter the next segment and start executing the callback function.

There is another point we need to pay special attention to when it comes to microtasks. That is, although nextTick is also a micro-task, its priority is higher than other micro-tasks. When executing a micro-task, only after all callback functions in nextlick are executed. Other microtasks will start to be executed.

In general, when the main thread synchronization code is executed, the microtasks will be cleared first (if the microtasks continue to generate microtasks, they will be cleared again), and then go to the next event loop 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 determine whether the microtask queue is empty. If it is empty, it will enter the next stage. Otherwise, the microtasks will be cleared first. queue.

Let’s use code practice to verify what we said above.

Code example

Execute synchronization first and then asynchronously

After the Node application starts , will not enter the event loop immediately, but will execute the synchronous code first, starting from top to bottom. The synchronous API will be executed immediately, and the asynchronous API will be handed over to the thread maintained by C for execution. The callback function of the asynchronous API will be registered to the corresponding event queue. middle. When all synchronization codes are executed, the event loop will be entered.

console.log("start");

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

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

console.log("end");

Let’s look at the execution results

In-depth understanding of Node event loop (EventLoop) mechanism

You can see that the synchronous code is executed first, and then the event loop is entered to execute the asynchronous code. In The timers stage executes two setTimeout callbacks.

Will setTimeout be executed before setImmediate?

We know that setTimeout is executed in the timers stage, setImmediate is executed in the check phase. And the event loop starts from the timers phase. Therefore, setTimeout will be executed first and then setImmediate.

Is the above analysis correct?

Let’s look at the 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

In-depth understanding of Node event loop (EventLoop) mechanism

Execute firstsetTimeoutthen execute setImmediate

Next let’s transform the above code, remove the delayer, and see what will be output

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

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

We ran it seven times and you can see it Twice, setImmediate

In-depth understanding of Node event loop (EventLoop) mechanism

was run first. What happened? Isn’t it first the timers stage and then the check stage? How could it change?

In fact, it depends on whether the asynchronous callback is fully ready when entering the event loop. For the initial example, because there is a delay of 2000 milliseconds, the setTimeout callback must be ready when entering the event loop. So the order of execution will not change. But for this example, because the main thread has no synchronization code to execute, it enters the event loop at the beginning. However, when entering the event loop, the callback of setTimeout is not necessarily fully prepared, so it will There is a callback function that first executes the setImmediate in the check stage, and then executes the setTimeout callback in the timers stage of the next event loop.

Under what circumstances will the setImmediate callback function take precedence over the setTimeout callback for the same delay time?

It's actually very simple, just put these two in the Pendingcallbacks, idle, prepare, poll between the timers stage and the check stage Any one of the stages will do. Because after these stages are executed, they will definitely go to the check stage and then the timers stage.

We take the poll 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");
  });
});

We also execute it seven times. You can see that setImmediate is executed first every time.

In-depth understanding of Node event loop (EventLoop) mechanism

So in general, with the same delay time, setTimeout is not 100% executed before setImmediate.

先微任务再宏任务

主线程同步代码执行完毕后,会先执行微任务再执行宏任务。

我们来看下面的例子

console.log("start");

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

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

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

console.log("end");

我们运行一下看结果,可以看到它是先执行了微任务然后再执行宏任务

In-depth understanding of Node event loop (EventLoop) mechanism

nextTick优于其它微任务

在微任务中nextTick的优先级是最高的。

我们来看下面的例子

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");

我们运行上面的代码,可以看到就算nextTick定义在resolve后面,它也是先执行的。

In-depth understanding of Node event loop (EventLoop) mechanism

微任务穿插在各个阶段间执行

怎么理解这个穿插呢?其实就是在事件循环的六个阶段每个阶段执行完后会清空微任务队列。

我们来看例子,我们建立了timers、check、poll三个阶段,并且每个阶段都产生了微任务。

// 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");
  });
});

我们来执行上面的代码

In-depth understanding of Node event loop (EventLoop) mechanism

可以看到,先执行微任务,再执行宏任务。先process.nextTick -> Promise.resolve。并且如果微任务继续产生微任务则会再次清空,所以就又输出了nextTick Promise.resolve

接下来到timer阶段,输出setTimeout,并且产生了一个微任务,再进入到下个阶段前需要清空微任务队列,所以继续输出setTimeout Promise.resolve

接下来到check阶段,输出setImmediate,并且产生了一个微任务,再进入到下个阶段前需要清空微任务队列,所以继续输出setImmediate Promise.resolve

这也就印证了微任务会穿插在各个阶段之间运行。

In-depth understanding of Node event loop (EventLoop) mechanism

总结

所以对于Node中的事件循环你只需要背好一以下几点就可以了

  • 当主线程同步代码执行完毕后才会进入事件循环

  • 事件循环总共分六个阶段,并且每个阶段都包括哪些回调需要记清楚。

  • 事件循环中会先执行微任务再执行宏任务。

  • 微任务会穿插在这六个阶段之间执行,每进入到下个阶段前会清空当前的微任务队列。

  • 微任务中process.nextTick的优先级最高,会优先执行。

更多node相关知识,请访问:nodejs 教程

The above is the detailed content of In-depth understanding of Node event loop (EventLoop) mechanism. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.cn. If there is any infringement, please contact admin@php.cn delete