Home  >  Article  >  Web Front-end  >  Detailed explanation of the event loop mechanism in nodejs

Detailed explanation of the event loop mechanism in nodejs

青灯夜游
青灯夜游forward
2021-04-29 10:47:252574browse

This article will take you to understand the event loop mechanism in node. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to everyone.

Detailed explanation of the event loop mechanism in nodejs

Front-end development is inseparable from JavaScript. JavaScript is a web front-end language, mainly used in web development and parsed and executed by the browser. The role of js is not only limited to front-end development, it can also be used for server-side development - nodejs. As a front-end person with ideals and ambitions, if you want to expand your horizons and master a server-side development language, nodejs is a very good choice.

Related recommendations: "nodejs Tutorial"

Because you have mastered the js development method, it is easy to get started with node, and the npm package management tool also greatly improves the development experience. Nodejs is famous for its asynchronous non-blocking I/O working method, and its processing mechanism is called the event loop.

Understanding the node event loop mechanism can better understand node's event processing method and the execution timing of asynchronous events. This article mainly explains the event loop mechanism of nodejs and lays the foundation for subsequent learning of node.

1. node VS javascript

As mentioned earlier, Javascript is a web front-end language, mainly used in web development, and is parsed and executed by the browser, while node .js is a JavaScript runtime environment based on the Chrome V8 engine. Therefore, nodejs is not a language, a library, or a framework, but a js runtime environment. Simply put, node can parse and execute js code. In the past, only browsers could parse and execute JS, but now node can make JS run completely without the browser.

There are many differences between node.js and browser js. For example, js in the browser includes ecmascript, BOM, and DOM, but js in nodejs does not have BOM and DOM, only emcscript. And the js execution environment of node provides some server-level operation APIs for js, such as: file reading and writing, network service construction, network communication, http server, etc. Most of these APIs are packaged into core modules. In addition, the event loop mechanism of node is different from the event loop mechanism of browser js.

2. JavaScript event loop

Everyone is already very clear about the js event loop in the browser. For the sake of comparison, I will briefly mention it here.

Detailed explanation of the event loop mechanism in nodejs

(Quoted from Philip Roberts’ speech "Help, I'm stuck in an event-loop")

When js is executed, synchronous and asynchronous tasks enter different execution environments respectively. Synchronous tasks enter the main thread, that is, the main execution stack, and asynchronous tasks (ajax requests, settimeout, setinterval, poromise.resolve(), etc.) enter the task queue. . Different asynchronous tasks will be pushed into different task queues, such as ajax requests, settimeout, setinterval, etc. These tasks will be pushed into the macro task queue (Macro Task), while the Promise function will be pushed into the micro task queue (Micro Task). . The overall event loop process is as follows:

  • When the synchronous code is executed, the main execution stack becomes empty and preparations begin to execute asynchronous tasks.

  • The main thread will check whether the microtask queue is empty. If it is not empty, it will traverse all the microtasks in the queue to execute them, clear the microtask queue, and then check the macro Task queue. If the microtask queue is empty, proceed directly to the next step.

  • The main thread traverses the macro task queue and executes the first macro task in the macro task queue. If it encounters a macro task or micro task during the execution, it will continue to process them. Push it into the corresponding task queue. Every time a macro task is executed, the micro task queue must be traversed and cleared.

  • Perform rendering operations and update the view

  • Start the next event loop and repeat the above steps until the two task queues are cleared

In order to deepen the impact, take a small example and look at the following code What will be output:

    var le=Promise.resolve(2);
    console.log(le)
    console.log('3')
    Promise.resolve().then(()=>{
    console.log('Promise1')  
    setTimeout(()=>{
        console.log('setTimeout2')
    },0)
    })
    setTimeout(()=>{
    console.log('setTimeout1')
    Promise.resolve().then(()=>{
        console.log('Promise2')    
    })
    },0);

Use the above event loop process to analyze:

  • js main process execution code encounters Promise.resolve(2), it will be executed immediately, and 2 will be changed. into a promise object, and then console.log(le) prints out the le variable, print----->Promise {: 2};
  • console.log('3') , print ----->3
  • Then continue the execution and encounter Promise.resolve().then, which is an asynchronous microtask function, pushed to the microtask stack
  • A function encounters setTimeout and pushes it to the macro task queue. At this point, the main process is empty.
  • Check the micro task queue and find Promise.resolve().then, so print ----->promise1 and encounter again to a timer, push it to the end of the macro task queue, the micro task queue is empty
  • Check the macro task queue, take the first macro task to execute, print ----->setTimeout1, and encounter again Promise.resolve().then, push it to the microtask queue
  • before starting the next macrotask, the microtask will be cleared, so after setTimeout1 is printed, the microtask queue will be checked, so ---> ;promise2
  • The next round of event loop takes the current first task of the macro task queue and executes it, then prints and prints ----->setTimeout2. At this point, both the macro task and micro task queues have been cleared. , the event loop ends

So the output result is: Promise {: 2}, 3, promise1, setTimeout1, promise2, setTimeout2.

The execution result in the browser is as follows:

Detailed explanation of the event loop mechanism in nodejs

3. Node event loop

There are total event loops of node Six stages, these six stages will be executed in order in an event loop until the event processing is completed. The sequence diagram of the six stages is as follows:

Detailed explanation of the event loop mechanism in nodejs

六个阶段分别是:

  • timers 阶段:这个阶段执行timer(setTimeout、setInterval)的回调
  • I/O callbacks 阶段:执行一些系统操作的回调(比如网络通信的错误回调);
  • idle, prepare 阶段:仅node内部使用,可忽略
  • poll 阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里
  • check 阶段:执行 setImmediate() 的回调
  • close callbacks 阶段:执行 socket 的 close 事件回调,如果一个socket或handle被突然关掉(比如socket.destroy()),close事件将在这个阶段被触发

事件循环中,每当进入某一个阶段,都会从该阶段对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,该阶段就会终止,然后检查NextTick队列和微任务队列,将其清空,之后进入下一个阶段。

这里面比较关键的是poll阶段:

  • poll队列不为空的时候,事件循环会遍历队列并同步执行回调,直到队列清空或执行回调数达到系统上限。
  • poll队列为空的时候,就会有两种情况:
    • 如果代码中存在setImmediate()回调,那么事件循环直接结束poll阶段进入check阶段来执行check队列里的回调;
    • 如果不存在setImmediate()回调,会等待回调被加入到队列中并立即执行回调,这里同样会有个超时时间设置防止一直等待下去,如果规定时间内有定时器函数进入队列,则返回到timer阶段,执行定时器回调,否则在poll阶段等待回调进入队列。

同样的举个大大的,看看以下代码会输出什么:

console.log('start')
setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(function() {
    console.log('promise1')
  })
}, 0)
setTimeout(() => {
  console.log('timer2')
  Promise.resolve().then(function() {
    console.log('promise2')
  })
}, 0)
Promise.resolve().then(function() {
  console.log('promise3')
})
console.log('end')

利用node事件循环分析呗:

  • 先执行同步任务,打印start,end
  • 进入timer阶段前,清空NextTick和micro队列,所以打印promise3
  • 进入timer阶段,打印timer1,并发现有一个微任务,立即执行微任务,打印promise1
  • 仍然在timer阶段,执行下个宏任务,打印timer2,同样遇到微任务,立即执行,打印promise2

因此输出顺序是:start,end,promise3,timer1,promise1,timer2,promise2,如果能正确回答出来说明对node的循环机制有了大体的了解,实际node输出结果确实是这样:

Detailed explanation of the event loop mechanism in nodejs

那如下代码会输出什么呢?

process.nextTick(function(){
    console.log(7);
});

new Promise(function(resolve){
    console.log(3);
    resolve();
    console.log(4);
}).then(function(){
    console.log(5);
});

process.nextTick(function(){
    console.log(8);
});

继续分析:

  • process.nextTick会将任务推进至nextTick队列,promise.then会把任务推至micro队列,上面提到过每次一个宏任务执行完,执行下一个宏任务之前需要清空nextTick队列和micro队列,同样的一个阶段执行完,进入下一个阶段之前也需要nextTick队列和micro队列,并且nextTick队列优先级高于micro队列
  • 先执行同步代码,打印3,4
  • 执行nextTick队列,打印7,8
  • 再执行micro队列,打印5

因此最终输出是:3,4,7,8,5,需要记住,process.nextTick 永远大于 promise.then的优先级

还有一个大家很容易混淆的点就是setTimout和setImmediate的执行时机,根据上面描述的node事件循环机制,setImmediate()应该在check阶段执行 与 而setTimeout在timer阶段执行,理论上setTimout比setImmediate先执行,看下面的代码:

setTimeout(() => console.log(1),0);
setImmediate(() => console.log(2));

执行结果是什么?1,2 还是 2,1,其实都有可能,看实际node运行的结果:

Detailed explanation of the event loop mechanism in nodejs

可以看到两次执行的结果不一样,为什么呢?原因在于即使setTimeout的第二个参数默认为0,但实际上,Node做不到0秒就执行其回调,最少也要4毫秒。那么进入事件循环后,如果没到4毫秒,那么timers阶段就会被跳过,从而进入check阶段执行setImmediate回调,此时输出结果是:2,1;

如果进入事件循环后,超过4毫秒(只是个大概,具体值并不确定),setTimeout的回调会出现在timer阶段的队列里,回调将被执行,之后再进入poll阶段和check阶段,此时输出结果是:1,2

那如果两者在I/O周期内调用,谁先执行呢?看一下代码:

const fs = require('fs')

fs.readFile('./test.txt', 'utf8' , (err, data) => {
  if (err) {
    console.error(err)
    return
  }
  setTimeout(() => {
    console.log('timeout');
  }, 0);
  setImmediate(() => {
    console.log('immediate');
  });
})

实际上,node中输出的结果总是immediate先输出,timeout后输出。因为I/O回调是在poll阶段执行,当回调执行完毕之后队列为空,发现存在setImmediate的回调就会进入check阶段,执行完毕后,再进入timer阶段。

四、总结

本文结合代码示例,对node的事件循环机制做了比较详细描述。通过这篇文章,应该可以了解浏览器的事件循环机制是怎样的,node的循环机制是怎样的,以及nextTick和micro队列的优先级,setTimout和setImmediate执行时机等一些容易混淆的知识点。文章中不足和不对之处,欢迎在评论区交流讨论,一起探索,谢谢。

更多编程相关知识,请访问:编程入门!!

The above is the detailed content of Detailed explanation of the event loop mechanism in nodejs. 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