laitimes

Event loops and microtasks in JS are fully resolved with macrotasks

author:Senior Internet Architect

introduce

First of all, everyone on earth knows that JS is single-threaded, so JS can only execute one task at the same time, that is, there is only one call stack, and the synchronous task is executed first, and then the asynchronous task is executed.

While HTML5 allows JS scripts to create multiple threads, subthreads are fully controlled by the main thread and must not manipulate the DOM. So, this new standard doesn't change the single-threaded nature of JS.

What is an asynchronous task?

Asynchronous tasks are those that are set aside by the engine and go into the task queue instead of going to the main thread.

What is an event loop

An Event Loop is a JavaScript engine's mechanism for handling asynchronous tasks. It is used to manage all task queues, including macro tasks and microtask queues. When the JavaScript engine encounters an asynchronous task, it places it in the appropriate task queue and continues to execute the synchronous code until the synchronous code is executed or the next asynchronous task is encountered. When the current macro task is executed, the JavaScript engine will take the task execution from the microtask queue according to certain rules until the microtask queue is empty, and then take the next macro task from the macro task queue to execute. This process is known as the event loop.

There are both macrotasks and microtasks

Macro tasks have:

Callback functions for events, new programs or subroutines are directly executed \<script>, setTimeout() and setInterval() requestAnimationFrame, i/o operations, setImmediate, UI rendering

Microtasks have:

promise.then() catch() finally() MutationObserver. Object.observe async/await node.js 中的process.nextTick() queueMicrotask()

How does an event loop count?

An Event Loop is a continuously running mechanism that continuously executes tasks in a task queue. A cycle of events typically consists of the following phases:

  1. Execute the current macro task: Fetch a macro task from the macro task queue for execution.
  2. Execute the microtasks generated in the current macro task: When a macro task is executed, all the queued microtasks are processed immediately and executed in the order in which they were added to the microtask queue.
  3. Update Rendering: If a page render is required, the corresponding rendering operation is performed.
  4. Check for Web Worker Tasks: If there are, execute the Web Worker tasks.
  5. Enter the next round of the event loop: Check if there are any new macro tasks that need to be executed. If so, skip to step 1, otherwise continue to wait for a new task to be added to the queue. The end of a cycle of events does not necessarily mean the end of the entire program, it is just the completion of a task following the above process. The event loop runs continuously, waiting for a new task to be added to the queue and executed as described above.

So the output order is synchronous task > asynchronous task (microtask > macro task)

or macro-task-> micro-task-> macro task

Code examples

<script>
console.log("Start");
  
setTimeout(function () {

console.log("这是定时器");

}, 0);
  
new Promise(() => {

console.log("这是Promise构造函数");

resolve();

}).then(() => {

console.log("这是Promise.Then");

});

console.log("End");
</script>

           

Event loop process

  • The overall script enters the main thread as the first macro task, encounters a console.log (Start), and outputs Start
  • When a setTimeout is encountered, its callback function is distributed to the macro task
  • 遇到 NewPrime,New Promise构造函数执行,输出"这是promis构造函数"
  • Encounters are then distributed into microtasks
  • 遇到console.log ("End"), 输出End
  • Once the call stack is emptied, the event loop prioritizes tasks in the microtask queue
  • We found that then inside the microtask, the execution output is "This is Promise.then"
  • The first round of the event loop ends, and the second round of the event loop begins
  • The macro task has a callback function corresponding to setTimeout, and the output "This is a timer" is immediately executed.

Output the result

  • Start
  • This is the Promise constructor
  • End
  • 这是Promise.Then
  • Here's the timer

This time, let's take a complicated example: macro task nested micro task micro task nested macro task This time we ignore the big macro task of script and start with the synchronous task perspective

async function async1() {

console.log("async1 start");

await async2();

console.log("async1 end");

}


async function async2() {

console.log("async2");

}

console.log("script start");

 

setTimeout(function () {

console.log("setTimeout1");

});

setTimeout(function () {

console.log("setTimeout2");

Promise.resolve().then(() => {

console.log("then1");

});

Promise.resolve().then(() => {

console.log("then2");

});

});

 
async1();

new Promise((res) => {

console.log("this is Promise");

res();

}).then(() => {

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

console.log("then3 setTimeout3");

});

});

console.log("script end");
           
  • Encountered function async1 async2 did not perform a skip
  • 遇到console.log('script start'),输出script start
  • setTimeout1 is encountered, and its callback function is distributed to the macro task
  • When setTimeout2 is encountered, its callback function is distributed to the macro task
  • 遇到async1()函数执行, 遇到console.log("async1 start"),输出async start
  • Encountered in async1 function: await, async2(), async2() takes precedence over await operator, async2() function execution
  • In the async2 function, console.log ("async2") is encountered to output async2
  • Going back to the async1 function, since the statement after the async function uses await will be put into a callback function, the await subsequent code is distributed to the microtask
  • 遇到new Promise构造函数中 console.log("this is Promise"),直接执行 输出this is Promise
  • The then method is distributed to the microtask
  • 遇 console.log("Script End")
  • After the synchronization task is executed, start executing the asynchronous task According to the eventloop, the microtask in the task queue is executed first
  • 任务队列先入先出 所以先输出'async1 end' 后输出 new Promise.then中的内容 then3,
  • new Promise.then()中遇到setTimeout放到宏任务队列中,
  • The first round of the event loop ends, and the second round begins
  • The macro task queue setTimeout1 is output
  • The second round of the event loop ends, and the third round begins
  • setTimeout2 执行 输出setTimeout1和setTimeout2
  • setTimeout2 has two .then methods distributed to the microtask, which then execute the output then1 and then2
  • The third round of the event loop ends, and the fourth round begins
  • The last macro task outputs then3 setTimeout3

So the code outputs the result

  • script start
  • async1 start
  • async2
  • this is Promise
  • script end
  • async1 end
  • then3
  • setTimeout1
  • setTimeout2
  • then1
  • then2
  • then3 setTimeout3

It is important to note that microtasks are executed in the order in which they were added to the microtask queue. Even if a microtask is generated later than other microtasks, if it is added to the queue earlier, it will still be executed before other microtasks.

That's the end of the article, I hope it helps you

作者:ZhaiMou

Link: https://juejin.cn/post/7330300019022970915