Detailed explanation of Event Loop execution mechanism

summary

JavaScript is a single threaded language, which means that all tasks must be queued one by one,
However, if a task takes too long to execute, it will block the execution of subsequent tasks,
Therefore, long-time tasks need to be treated as asynchronous tasks to prevent the main thread from blocking,
So there is the execution mechanism of Event Loop

js specific execution process:

When setTimeout (FN, 1000) or setTimeout (FN, 0) is executed, it will be put into the Event Table to register the callback function. After 1000ms or 0 milliseconds (the actual minimum delay is 4ms), the callback function will be put into the Event Queue. Remember that the callback result in the Event Queue will be read and put into the main thread for execution only after all tasks in the main thread are executed

Macro and micro tasks

Asynchronous tasks have more detailed definitions:

  • Macro task: code block, setImmediate, setTimeout, setInterval, I/O, UI interaction event
  • Micro task: promise, progress nextTick MutaionObserver

In addition, new prime() will immediately call the callback function inside, which should belong to the synchronization task

The execution sequence of all tasks in the Event Queue is:

  1. Execute the code block first (tasks in the main program, personal understanding is to execute all synchronization tasks first);
  2. Then start executing all micro tasks in the queue,
  3. Then execute the next macro task again,
  4. Then repeat step 2 until the task queue is empty
console.log('1');

// setTimeout1
setTimeout(function() {
   console.log('2');
   // promise1
   new Promise(function(resolve) {
       console.log('3');
       resolve();
   }).then(function() {
       console.log('4')
   })
})
// promise
new Promise(function(resolve) {
   console.log('5');
   resolve();
}).then(function() {
   console.log('6')
})

// setTimeout2
setTimeout(function() {
   console.log('7');
   // promise2
   new Promise(function(resolve) {
       console.log('8');
       resolve();
   }).then(function() {
       console.log('9')
   })
})

According to the above code, analyze the execution steps (in chrome environment):

  1. First execute the overall code (tasks in the main thread)
console.log(1)
promise
// Output 1, 5
  1. Then start executing the micro tasks in the queue
promise.then
// Output 6
  1. Then execute the next macro task setTimeout1
console.log('2');
promise1
// Output 2, 3
  1. Then execute the micro task in the setTimeout1 code block
promise1.then
// Output 4
  1. Then execute the next macro task setTimeout2. This step is the same as step 3:

Output 7,8,9

So the final output is: 1,5,6,2,3,4,7,8,9

Think about the output of the following code

<div class="outer">
  <div class="inner"></div>
</div>
// Let's get hold of those elements
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');

// Let's listen for attribute changes on the
// outer element
new MutationObserver(function () {
  console.log('mutate');
}).observe(outer, {
  attributes: true,
});

// Here's a click listener...
function onClick() {
  console.log('click');

  setTimeout(function () {
    console.log('timeout');
  }, 0);

  Promise.resolve().then(function () {
    console.log('promise');
  });

  outer.setAttribute('data-random', Math.random());
}

// ...which we'll attach to both elements
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);

When you click inner div:

  1. Execute onClick callback: console log('click');
  2. Then execute the callback in the micro task queue: console log('promise'); console. log('mutate');
  3. Due to the bubble mechanism of onClick event, execute onClick (click event of outer DIV) again and execute: console log('click');
  4. Then execute the callback in the micro task queue again: console log('promise'); console. log('mutate');
  5. Then execute setTimeout

So the output results are: onClick,promise,mutate,onClick,promise,mutate,timeout,timeout. Other browsers will be different
See the examples in this article for the specific implementation process Tasks, microtasks, queues and schedules There are detailed implementation steps

Own understanding

Node environment

If we add the above code to progress What will be output if nexttick is put into the node environment for execution?

console.log('1');

// setTimeout1
setTimeout(function () {
  console.log('2');
  // promise1
  new Promise(function (resolve) {
    console.log('4');
    resolve();
  }).then(function () {
    console.log('5')
  })
  // process.nextTick1
  process.nextTick(function () {
    console.log('3');
  })
})

// promise
new Promise(function (resolve) {
  console.log('7');
  resolve();
}).then(function () {
  console.log('8')
})
// process.nextTick
process.nextTick(function () {
  console.log('6');
})

// setTimeout2
setTimeout(function () {
  console.log('9');
  // promise2
  new Promise(function (resolve) {
    console.log('11');
    resolve();
  }).then(function () {
    console.log('12')
  })
  // process.nextTick2
  process.nextTick(function () {
    console.log('10');
  })
})

According to the above logic, we can get the results of 1,9,7,7,7,7,7,6,7,7

But it's really slapped in the face again immediately. The direct output is: 1,7,6,8,2,4,3,5,9,11,10,12

By comparing the results, it is found that progress Nexttick takes precedence over promise

The above code is in node version V12 Tested on 10.0, but in v10.0 15.3 when tested on the, it will be found that the output:
1,7,6,8,2,4,9,11,3,10,5,12
**However, if the second setTimeout setting is delayed by more than 1ms, the result will follow version V12 Consistent output on 10.0**

I wipe. What the hell is this? Is it a little confused? From the above output results, we can know that when the setTimeout delay is the same, they will merge (let's understand this):
Use code to explain as follows:

setTimeout(function () {
  
  console.log('2');
  // promise1
  new Promise(function (resolve) {
    console.log('4');
    resolve();
  }).then(function () {
    console.log('5')
  })
  // process.nextTick1
  process.nextTick(function () {
    console.log('3');
  })
  
  console.log('9');
  // promise2
  new Promise(function (resolve) {
    console.log('11');
    resolve();
  }).then(function () {
    console.log('12')
  })
  // process.nextTick2
  process.nextTick(function () {
    console.log('10');
  })

  
})

After merging, the codes are put together, and the output during execution is: 2,4,9,11,3,10,5,12. Therefore:

When the setTimeout delay is the same, they will be merged and executed together

Event Loop execution mechanism of Node

reference material

Tags: Javascript Front-end

Posted by kumschick on Sat, 07 May 2022 11:13:09 +0300