Home  >  Article  >  Web Front-end  >  JavaScript execution mechanism and event loop in nodejs environment

JavaScript execution mechanism and event loop in nodejs environment

不言
不言forward
2019-04-02 10:59:592491browse

This article brings you an introduction to the usage of JavaScript high-order functions. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.

1. Description

nodejs is executed in a single thread, and it is based on an event-driven non-blocking IO programming model. This allows us to continue executing the code without waiting for the asynchronous operation result to return. When an asynchronous event is triggered, the main thread will be notified, and the main thread will execute the callback of the corresponding event.

This article explains the execution process of JavaScript code in node. The following is the test code. If you know the output result, then you don’t need to read this article. If you don’t know the output result, then this video The article can help you understand:

console.log(1)
setTimeout(function () {
  new Promise(function (resolve) {
    console.log(2)
    resolve()
  })
  .then(() => { console.log(3) })
})
setTimeout(function () {
  console.log(4)
})

Complex:

setTimeout(() => {
  console.log('1')
  new Promise((resolve) => { console.log('2'); resolve(); })
  .then(() => { console.log('3') })
  new Promise((resolve)=> { console.log('4'); resolve()})
  .then(() => { console.log('5') })
  setTimeout(() => { 
    console.log('6')
    setTimeout(() => {
      console.log('7')
      new Promise((resolve) => { console.log('8'); resolve() })
      .then( () => {  console.log('9') })
      new Promise((resolve) => { console.log('10'); resolve() })
      .then(() => {  console.log('11') })
    })
    setTimeout(() => { console.log('12') }, 0)
  })
  setTimeout(() => { console.log('13') }, 0)
})
setTimeout(() => { console.log('14') }, 0)
new Promise((resolve) => { console.log('15'); resolve() })
.then( ()=> { console.log('16') })
new Promise((resolve) => { console.log('17'); resolve() })
.then(() => { console.log('18') })

2. The startup process of nodejs

The node.js startup process can be divided into the following Steps:

  1. Call the platformInit method to initialize the nodejs running environment.
  2. Call the performance_node_start method to perform performance statistics on nodejs.
  3. Judgement of openssl settings.
  4. Call v8_platform.Initialize to initialize the libuv thread pool.
  5. Call V8::Initialize to initialize the V8 environment.
  6. Create a nodejs running instance.
  7. Start the instance created in the previous step.
  8. Start executing the js file. After the synchronization code is executed, enter the event loop.
  9. When there are no events to listen to, the nodejs instance is destroyed and the program execution is completed.

JavaScript execution mechanism and event loop in nodejs environment

3. Detailed explanation of nodejs event loop

Nodejs subdivides the message loop into 6 stages (official Called Phase), each stage will have a queue-like structure that stores the callback functions that need to be processed in that stage.

Nodejs In order to prevent too many tasks in a certain stage from causing starvation in subsequent stages , so in each iteration of the message loop, there is a maximum number of callbacks executed in each stage. If the number is exceeded, the current stage will be forcibly ended and the next stage will be entered. This rule applies to every callback in the message loop. One stage.

3.1 Timer stage

This is the first stage of the message loop, using a for loop to process all setTimeout and setInterval callbacks.

These callbacks are saved in a minimum heap (min heap). In this way, the engine only needs to judge the header element each time, and if it meets the conditions, it will be taken out and executed until it encounters a The Timer Phase ends only when the conditions are not met or the queue is empty.

The method to determine whether a certain callback meets the conditions in the Timer phase is also very simple. The message loop will save the current system time every time it enters the Timer. , and then just check whether the startup time set by the callback function in the above minimum heap exceeds the time saved when entering the Timer. If it exceeds, take it out and execute it.

3.2 Pending I/O Callback Phase

Execute almost all callbacks except close callbacks, setTimeout(), setInterval(), setImmediate() callbacks, for exampleTCP connection error, fs.read, socket and other callback functions for IO operations, as well as callbacks for various errors.

3.3 Idle, Prepare phase

Some calls within the system.

3.4 Poll stage, important stage

This is the most important stage in the entire message cycle. Its function is to wait for asynchronous requests and data, because it supports the entire message cycle mechanism.

The poll phase has two main functions: one is to execute the callback of timers whose lower limit time has been reached, and the other is to process events in the poll queue.
Note: Many APIs of Node are based on event subscription, such as fs.readFile. These callbacks should be completed in the poll stage.

When the event loop enters the poll phase:

  • #pollWhen the queue is not empty, the event loop must first traverse the queue and execute the callback synchronously until the queue The number of cleared or executed callbacks reaches the system limit.
  • pollWhen the queue is empty, there are two situations.

    • If the code has been set with a callback by setImmediate(), then the event loop directly ends the poll stage and enters the check stage. Execute the callback in the check queue.
    • If the code is not setsetImmediate()Set callback:

      • If there are timers set, then the event loop will check the timers at this time. If one or more timers have reached the lower limit, then the event loop will wrap back to the timers stage and execute the effective callback of the timers. queue.
      • If timers are not set, the event loop is blocked at this time Waiting in the poll phase for the event callback to be added to the poll queue.

In the Poll phase, when none of the event callbacks registered by the js layer code returns, the event loop will temporarily block in the poll phase and unblock it. Conditions:

  1. When the poll phase is executed, a timeout timeout will be passed in, which is the maximum blocking time of the poll phase.
  2. When the timeout time has not expired, if an event returns, the callback function registered for the event will be executed. When the timeout timeout expires, the poll phase will exit and the next phase will be executed.

What is the appropriate setting for this timeout? The answer is the difference between the start time of the callback to be executed recently in the Timer Phase and the present. Assume that the difference is detal. Because there is nothing waiting to be executed after the Poll Phase. Callback. So the maximum waiting time here is delta. If an event wakes up the message loop during the period, then continue the work of the next Phase; if nothing happens during the period, then after timeout, the message loop still has to enter the subsequent Phase, so The Timer Phase of the next iteration can also be executed.
Nodejs drives the entire message loop through the Poll Phase, waiting for IO events and the arrival of kernel asynchronous events.

3.5 Check phase

This stage only handles the callback function of setImmediate.
So why is there a special stage to handle setImmediate? Simply put, it is because the Poll stage may set some callbacks and hope to run after the Poll stage . So the Check stage is added after the Poll stage.

3.6 Close Callbacks stage

Specializes in handling some close type callbacks. For example, socket.on('close', .. .). Used for resource cleaning.

4. Nodejs execution JS code process and event loop process

1. Node initialization

Initialize node environment

Execute the entered code

Executeprocess.nextTickCallback

Execute microtasks

2. Enter the event loop

2.1. Entering the Timer stage

  • Check whether the Timer queue has an expired Timer callback. If so, Execute all expired Timer callbacks in ascending order by TimerId
  • Check whether there are process.nextTick tasks, if so, execute them all
  • Check whether there are microtasks (promise), if so, execute them all
  • Exit this stage

2.2, enter Pending I/O Callback Phase

  • Checks whether there is a callback for Pending I/O Callback, and if so, executes the callback. If you do not exit this stage
  • Check whether there are process.nextTick tasks, if so, execute them all
  • Check whether there are microtasks (promise) , if so, execute all
  • Exit this stage

2.3. Enter idle, prepare stage

This stage has little to do with JavaScript , skip

2.4 and enter the Poll stage

First check whether there is an unfinished callback. If it exists, there are two situations:

The first case: there is an executable callback

Execute all available callbacks (including expired timers and some IO events, etc.)

Check whether there is process.nextTickTasks, if there are any, execute them all

Check whether there are microtasks (promise), if there are, execute them all

Exit this stage

Second type Situation: There is no executable callback

Check if there is an immediate callback, if so, exit the Poll phase. If not, block in this stage, waiting for new event notification

If there is no unfinished callback, exit the Poll stage

2.5, entercheckPhase

If there is an immediate callback, execute all immediate callbacks

Check whether there are process.nextTick tasks, if so, execute them all

Check whether there are microtasks (promise), if so, execute them all

Exit this stage

2.6, enter the closing stage

if If there is an immediate callback, execute all immediate callbacks

Check whether there are process.nextTick tasks, if so, execute them all

Check whether there are microtasks (promise), if Yes, execute all

Exit this stage

3. Check whether there are active handles (timer, IO and other event handles)

If there are , continue the next round of event loop

If not, end the event loop and exit the program

Note:

Each sub-stage of the event loop will execute the following process in sequence before exiting:

Check whether there is a process.nextTick callback, and if so, execute them all.

Check whether there are microtasks (promise), and if so, execute them all.

4.1 关于Promise和process.nextTick

事件循环队列先保证所有的process.nextTick回调,然后将所有的Promise回调追加在后面,最终在每个阶段结束的时候一次性拿出来执行。

此外,process.nextTickPromise回调的数量是受限制的,也就是说,如果一直往这个队列中加入回调,那么整个事件循环就会被卡住

JavaScript execution mechanism and event loop in nodejs environment

4.2 关于setTimeout(…, 0) 和 setImmediate

这两个方法的回调到底谁快?

如下面的例子:

setImmediate(() => console.log(2))
setTimeout(() => console.log(1))

使用nodejs多次执行后,发现输出结果有时是1 2,有时是2 1

对于多次执行输出结果不同,需要了解事件循环的基础问题。

首先,Nodejs启动,初始化环境后加载我们的JS代码(index.js).发生了两件事(此时尚未进入消息循环环节):

setImmediate 向 Check 阶段 中添加了回调 console.log(2);

setTimeout 向 Timer 阶段 中添加了回调 console.log(1)

这时候, 要初始化阶段完毕, 要进入 Nodejs 消息循环了。

为什么会有两种输出呢? 接下来一步很关键:

当执行到 Timer 阶段 时, 会发生两种可能. 因为每一轮迭代刚刚进入 Timer 阶段 时会取系统时间保存起来, 以 ms(毫秒) 为最小单位.

如果 Timer 阶段 中回调预设的时间 > 消息循环所保存的时间, 则执行 Timer 阶段 中的该回调. 这种情况下先输出 1, 直到 Check 阶段 执行后,输出2.总的来说, 结果是 1 2.

如果运行比较快, Timer 阶段 中回调预设的时间可能刚好等于消息循环所保存的时间, 这种情况下, Timer 阶段 中的回调得不到执行, 则继续下一个 阶段. 直到 Check 阶段, 输出 2. 然后等下一轮迭代的 Timer 阶段, 这时的时间一定是满足 Timer 阶段 中回调预设的时间 > 消息循环所保存的时间 , 所以 console.log(1) 得到执行, 输出 1. 总的来说, 结果就是 2 1.

所以, 输出不稳定的原因就取决于进入 Timer 阶段 的时间是否和执行 setTimeout 的时间在 1ms 内. 如果把代码改成如下, 则一定会得到稳定的输出:

require('fs').readFile('my-file-path.txt', () => {
 setImmediate(() => console.log(2))
 setTimeout(() => console.log(1))
});

这是因为消息循环在 Pneding I/O Phase 才向 Timer 和 Check 队列插入回调. 这时按照消息循环的执行顺序, Check 一定在 Timer 之前执行。

从性能角度讲, setTimeout 的处理是在 Timer Phase, 其中 min heap 保存了 timer 的回调, 因此每执行一个回调的同时都会涉及到堆调整. 而 setImmediate 仅仅是清空一个队列. 效率自然会高很多.

再从执行时机上讲. setTimeout(..., 0) 和 setImmediate 完全属于两个阶段.

5. 一个实际例子演示

下面以一段代码来说明nodejs运行JavaScript的机制。

如下面一段代码:

setTimeout(() => {                                                // settimeout1
  console.log('1')
  new Promise((resolve) => { console.log('2'); resolve(); })      // Promise3
  .then(() => { console.log('3') })
  new Promise((resolve)=> { console.log('4'); resolve()})         // Promise4
  .then(() => { console.log('5') })
  setTimeout(() => {                                              // settimeout3
    console.log('6')
    setTimeout(() => {                                            // settimeout5
      console.log('7')
      new Promise((resolve) => { console.log('8'); resolve() })   // Promise5
      .then( () => {  console.log('9') })
      new Promise((resolve) => { console.log('10'); resolve() })  // Promise6
      .then(() => {  console.log('11') })
    })
    setTimeout(() => { console.log('12') }, 0)                    // settimeout6
  })
  setTimeout(() => { console.log('13') }, 0)                      // settimeout4
})
setTimeout(() => { console.log('14') }, 0)                        // settimeout2
new Promise((resolve) => { console.log('15'); resolve() })        // Promise1
.then( ()=> { console.log('16') })
new Promise((resolve) => { console.log('17'); resolve() })        // Promise2
.then(() => { console.log('18') })

上面代码执行过程:

node初始化

执行JavaScript代码

遇到setTimeout, 把回调函数放到Timer队列中,记为settimeout1

遇到setTimeout, 把回调函数放到Timer队列中,记为settimeout2

遇到Promise,执行,输出15,把回调函数放到微任务队列,记为Promise1

遇到Promise,执行,输出17,把回调函数放到微任务队列,记为Promise2

代码执行结束,此阶段输出结果:15 17

没有process.nextTick回调,略过

执行微任务

检查微任务队列是否有可执行回调,此时队列有2个回调:Promise1、Promise2

执行Promise1回调,输出16

执行Promise2回调,输出18

此阶段输出结果:16 18

进入第一次事件循环

进入Timer阶段

检查Timer队列是否有可执行的回调,此时队列有2个回调:settimeout1、settimeout2

执行settimeout1回调:

输出1、2、4

添加了2个微任务,记为Promise3、Promise4

添加了2个Timer任务,记为settimeout3、settimeout4

执行settimeout2回调,输出14

Timer队列任务执行完毕

没有process.nextTick回调,略过

检查微任务队列是否有可执行回调,此时队列有2个回调:Promise3、Promise4

按顺序执行2个微任务,输出3、5

此阶段输出结果:1 2 4 14 3 5

Pending I/O Callback阶段没有任务,略过

进入 Poll 阶段

检查是否存在尚未完成的回调,此时有2个回调:settimeout3、settimeout4

Execute settimeout3 callback

Output 6

Added 2 Timer tasks, recorded as settimeout5, settimeout6

Execute settimeout4 callback, output 13

There is no process.nextTick callback, skip

There is no microtask, skip

The output result of this stage: 6 13

There are no tasks in the check and closing phases, skip them

Check whether there are still active handles (timers, IO and other event handles) , yes, continue to the next round of event loop

Enter the second event loop

Enter the Timer phase

Check whether the Timer queue has an executable callback, this The queue has 2 callbacks: settimeout5, settimeout6

Execute settimeout5 callback:

Output 7, 8, 10

Added 2 microtasks , recorded as Promise5, Promise6

executes the settimeout6 callback, outputs 12

There is no process.nextTick callback, skip

Check whether the microtask queue has executable callbacks. At this time, the queue has 2 callbacks: Promise5, Promise6

Execute 2 microtasks in order, output 9, 11

Output results of this stage: 7 8 10 12 9 11

There are no tasks in the Pending I/O Callback, Poll, check, and closing phases, so skip them

Check if there are still active handles (timer, IO and other event handles), if there are no more, end the event loop and exit the program

End of program execution , output result: 15 17 16 18 1 2 4 14 3 5 6 13 7 8 10 12 9 11

JavaScript execution mechanism and event loop in nodejs environment

[Related recommendations: JavaScript video tutorial

The above is the detailed content of JavaScript execution mechanism and event loop in nodejs environment. For more information, please follow other related articles on the PHP Chinese website!

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