Pay attention to

In Node 11, Node’s Event Loop has become similar to the browser’s.

background

Event Loop is also a topic that js has always talked about. At the end of February, I read teacher Ruan Yifeng’s “Detailed Explanation of Node Timer”, and found that I could not completely standard the JS event cycle execution mechanism I had seen before. I also looked up some other information and made notes. I felt inappropriate and summarized it into a paper.

Event loops and execution mechanisms are different in browsers and Nodes and should not be confused. The browser Event loop is a specification defined in HTML5, while node is implemented by the Libuv library. At the same time, when I read the book “Simple nodeJs”, I found that the node mechanism has been different at that time, so the node part of this article is for the version published in this article. I highly recommend reading the first three articles in the reference link.

Browser environment

Js is executed as a single thread (regardless of web workers) and all code is executed on the main thread call stack. Tasks in the task queue are polled only when the main thread is empty.

Task queue

Asynchronous tasks are classified into tasks (macrotasks, also known as macrotasks) and microtasks (microtasks). Task queues (Macrotask Queues) and MicroTask queues are called task queues (Macrotask Queues) and microTask queues (Macrotask Queues).

  • Task: Script code, setTimeout, setInterval, I/O, UI render.
  • Microtask: Promise, Object.observe, MutationObserver.

The specific process

  1. Completes the task in the main execution thread.
  2. Remove tasks from the Microtask Queue and execute them until they are empty.
  3. Retrieve a task from the Macrotask Queue.
  4. Remove tasks from the Microtask Queue and execute them until they are empty.
  5. Repeat 3 and 4.

That is, for synchronous completion, one macro task, all microtasks, one macro task, all microtasks……

Pay attention to

  • In the browser page, it can be considered that there is no code in the initial execution thread, and the code in each script tag is an independent task, that is, the microtask created in the previous script will be executed before the synchronization code in the later script is executed.
  • If microTasks are added all the time, microTask execution continues, and MacroTask is “stuck.”
  • The execution sequence of some browsers is inconsistent with the above, which may be inconsistent with the standards or conflicts between JS and HTML standards. You can read the first article in the Resources article.
  • New Promise((resolve, reject) =>{console.log(' sync '); Resolve ()}).then(() => {console.log(' async ')}), i.e.,promisethethenandcatchIt is MicroTask, not its own internal code.
  • Individual browser-specific apis are not listed.

Pseudo code

while (true) {macro task queue. Shift () microtask queue all tasks ()}Copy the code

The node environment

Js is executed as a single thread, and all code is executed in the main thread call stack. Tasks in the task queue are polled only when the main thread is empty.

Cycle stages

Each cycle of events in Node is divided into six phases in order, from the implementation of Libuv:

  1. Timers: Executes setTimeout and setInterval callbacks that meet the conditions.
  2. I/O callbacks: Whether there are callbacks to completed I/O operations, poll residue from the previous round.
  3. Idle, prepare: can be ignored
  4. Poll: Indicates that the wait for unfinished I/O events ends due to timers and the timeout period.
  5. Check: Executes the setImmediate callback.
  6. Close callbacks: Close all closing handles, some onclose events.

Enforcement mechanism

Several queue

In addition to the task types in the loop phase above, we are left with microtasks shared by browsers and Nodes, and Process. nextTick, which is unique to Node, which we call microTask Queue and nextTick Queue.

We also call the execution queues of several stages of the loop as Timers Queue, I/O Queue, Check Queue, and Close Queue respectively.

Before the loop

Before entering the first loop, the following is done:

  • Synchronization task
  • Make an asynchronous request
  • This section describes how to set the timer validity time
  • performprocess.nextTick()

Start cycle

Execute each of the six phases of the cycle, clearing the NextTick Queue and the Microtask Queue. Then the next stage is executed. After the completion of all six stages, the next cycle is entered. That is:

  • Clear Timers Queue, NextTick Queue, and Microtask Queue in the current loop.
  • Clear the I/O Queue, NextTick Queue, and Microtask Queue in the current loop.
  • Clear the Check Queu, NextTick Queue, and Microtask Queue in the current loop.
  • Clear the Close Queu, NextTick Queue, and Microtask Queue in the current loop.
  • Enter the next cycle.

As you can see, nextTick has a higher priority than Microtasks such as Promise. SetTimeout and setInterval have a higher priority than setImmediate.

Pay attention to

  • If created during the timers phasesetImmediateIs executed during the Check phase of this cycle, if created during timerssetTimeoutBecause the timers have been removed, the system enters the next cycle. The same is true for creating timers in the Check phase.
  • setTimeoutPriority thansetImmediateHigh, but becausesetTimeout(fn,0)The true delay of the can not be exactly 0 seconds, may appear first createdsetTimeout(fn,0)And thansetImmediateIs executed after the callback.

Pseudo code

while (true) {loop. ForEach ((phase) => {phase all tasks () nextTick all tasks () microTask all tasks ()}) loop = loop.next}Copy the code

The test code

function sleep(time) {
  let startTime = new Date()
  while (new Date() - startTime < time) {}
  console.log('1s over')}setTimeout(() => {
  console.log('setTimeout - 1')
  setTimeout(() => {
      console.log('setTimeout - 1 - 1')
      sleep(1000)
  })
  new Promise(resolve => resolve()).then(() => {
      console.log('setTimeout - 1 - then')
      new Promise(resolve => resolve()).then(() => {
          console.log('setTimeout - 1 - then - then')
      })
  })
  sleep(1000)
})

setTimeout(() => {
  console.log('setTimeout - 2')
  setTimeout(() => {
      console.log('setTimeout - 2 - 1')
      sleep(1000)
  })
  new Promise(resolve => resolve()).then(() => {
      console.log('setTimeout - 2 - then')
      new Promise(resolve => resolve()).then(() => {
          console.log('setTimeout - 2 - then - then')
      })
  })
  sleep(1000)
})
Copy the code
  • Browser output:
    setTimeout-1 //1 indicates a single task 1s oversetTimeout - 1 - then
    setTimeout - 1 - then - then 
    setTimeout-2 //2 indicates a single task 1s oversetTimeout - 2 - then
    setTimeout - 2 - then - then
    setTimeout - 1 - 1
    1s over
    setTimeout - 2 - 1
    1s over
    Copy the code
  • The node output:
    setTimeout - 1 
    1s over
    setTimeout-2 //1 and 2 are single-phase task 1s oversetTimeout - 1 - then
    setTimeout - 2 - then
    setTimeout - 1 - then - then
    setTimeout - 2 - then - then
    setTimeout - 1 - 1
    1s over
    setTimeout - 2 - 1
    1s over
    Copy the code

This also shows the difference between the event loop in the browser and node.

The execution of the new Node is the same as that of the browser. In the browser environment, the console output is used as an example to refer to the function. The execution process is as follows

<! -- Completes the task in the main execution thread. -- > <! Remove tasks from the Microtask Queue until they are empty. -- > <! Retrieve a task from the Macrotask Queue. -- > <! Remove tasks from the Microtask Queue until they are empty. -- > <! Repeat 3 and 4. --> IQ refers to micro task queue, AQ refers to macro task queue 1. Finish executing tasks in the main thread: The main thread has finished executing,setTimeout - 1.setTimeout-2 Enter wait 2. Clear IQ: there is no task in IQ 2. Perform a mission in AQ:setWhen timeout-1 reaches the time, it enters AQ and is executedsetTimeout-1-1 Enters the wait state,setTimeout-1-then directly enters the IQ queue becausesetTimeout-1 waits for 1ssetTimeout-2 must have entered AQ,setQ: [setTimeout - 1 - then], AQ:setTimeout-2, setTimeout-1-1] 3. Clear IQ: the IQ has been clearedsetTimeout - 1 - then, executionsetTimeout-1-then, during execution,setTimout-1-then-then is added directly to IQ, so IQ is not empty, so continue executionsetTimout-1-then-then, IQ is cleared, and the end state is IQ: [], AQ: [setTimeout-2, setTimeout-1-1] 4. Execute a task in AQ: executesetTimeout-2 5. Clear IQ: This step is similar to 3, so outputsetTimeout - 2 - then,setIQ: [], AQ: [setTimeout-1-1, setTimeout-2-1] 6. Perform a task in AQ: i.esetTimeout-1-1 7. Empty IQ: empty itself 8. Perform a task in AQ: i.esetTimeout-2-1
Copy the code

Refer to the article

  • Tasks, Microtasks, Queues and schedules are highly recommended
  • Don’t confuse NodeJS with event loops in browsers highly recommended
  • The Event module in Node is highly recommended
  • Understanding the Cycle of Events 1 (Analysis)
  • Node timer description