Detailed explanation of js event loop mechanism and interview questions

JavaScript is a single-threaded program, that is, it has only one main line, and all programs are "queued" for execution line by line. In this case, there may be some problems, such as setTimeout and ajax waiting for a long time to execute. It will block the execution of subsequent code, making the execution of the entire program take a very long time. In order to deal with such a problem, there are several "channels" when the JavaScript code is executed.

The first is the call stack, which performs operations that take a short time. Operations that take a long time are first placed in the task queue. The task queue is divided into macro tasks (macro-task) and micro tasks (micro-task). In micro tasks, Promise.then, aysnc, and await are placed in the queue. In this way, setTimeout, ajax, and onClick events are placed in the macro task queue. After the task execution of the call stack is completed, the micro task queue is polled. The task execution in the micro task queue is completed. Then execute the macro task.

Stack and queue are mentioned here. Let’s briefly talk about these two data structures. The stack is a last-in-first-out structure. It can only be entered from the tail and deleted from the tail. To use an analogy from a scene in life, it is like a buffet. The plate placed first is at the bottom, and the plate placed last is at the top. The top plates need to be taken away one by one to get to the bottom plate.

The queue is a first-in-first-out structure. Enter from the tail and delete from the head. Just like when we queue to buy something, the students who go first can buy it first.

Returning to the event loop mechanism (event loop), the program that does not block the main process is put into the call stack and pushed to the bottom of the stack. It will pop up after execution. If it is a function, then all the contents in the function will not pop up until it is executed. The programs that block the main process are put into the task queue, and they need to be "queued" for execution in sequence.

The above is like this, bah bah bah bah bah, you can listen to it. Previous case

First, let’s take a simple example to determine the execution order of the following programs.

new Promise(resolve => {
  console.log('promise');
  resolve(5);
}).then(value=>{
  console.log('then回调', value)
})
 
function func1() {
  console.log('func1');
}
 
setTimeout(() => {
  console.log('setTimeout');
});
 
func1();

Creating an instance of promise is to start an asynchronous task. The incoming callback function, also called the excutor function, will be executed immediately, so enter promise and use resolve to return a successful execution result. The execution in the then function will be pushed to the microprocessor. Wait for the call stack execution to be completed in the task queue before executing it sequentially.

When executing downward, it is found that a function func1 is defined. If the function is not called at this time, it will not be pushed into the call stack for execution. The program continues, and it is found that the setTimeout function is called, and the printed setTimeout is pushed into the macro task queue, and then the function func1 is called, func1 is pushed into the call stack, the func1 function is executed, and fun1 is output.

All contents in the call stack are executed, start polling the microtask queue, enter then callback 5, and finally execute the macrotask queue, enter setTimeout

Let’s look at a more complex example

setTimeout(function () {
  console.log("set1");
  new Promise(function (resolve) {
    resolve();
  }).then(function () {
    new Promise(function (resolve) {
      resolve();
    }).then(function () {
      console.log("then4");
    });
    console.log("then2");
  });
});
 
new Promise(function (resolve) {
  console.log("pr1");
  resolve();
}).then(function () {
  console.log("then1");
});
 
setTimeout(function () {
  console.log("set2");
});
 
console.log(2);
 
queueMicrotask(() => {
  console.log("queueMicrotask1")
});
 
new Promise(function (resolve) {
  resolve();
}).then(function () {
  console.log("then3");
});



结果

pr1
2
then1
queueMicrotask1
then3
set1
then2
then4
set2

The callback function ("set1") executed by setTimeout is directly placed in the macro task queue to wait. The Promise's executor function is executed immediately. First, enter pr1, and the Promise.then function ("then1") is placed in the micro task queue to wait. The following The callback function ("set2") executed by setTimeout is also placed in the macro task queue, ranked behind ("set1"), and then the call stack outputs 2. queueMicrotask means starting a microtask, which is consistent with the effect of the Promise.then function. , ("queueMicrotask1") is put into the microtask queue, and then executed, the executor function of the new Promise is executed immediately, and the then function ("then3") is placed in the microtask queue to wait. At this time, the call stack has entered pr1 in sequence. ,2.

Creating an instance of promise is to start an asynchronous task. The incoming callback function, also called the excutor function, will be executed immediately, so enter promise and use resolve to return a successful execution result. The execution in the then function will be pushed to the microprocessor. Wait for the call stack execution to be completed in the task queue before executing it sequentially.

When executing downward, it is found that a function func1 is defined. If the function is not called at this time, it will not be pushed into the call stack for execution. The program continues, and it is found that the setTimeout function is called, and the printed setTimeout is pushed into the macro task queue, and then the function func1 is called, func1 is pushed into the call stack, the func1 function is executed, and fun1 is output.

All contents in the call stack are executed, start polling the microtask queue, enter then callback 5, and finally execute the macrotask queue, enter setTimeout

A simple diagram is as follows

The last question added async and await.

Let’s start with a conclusion. Functions defined by async are executed in the call stack. Await turns an asynchronous program into a synchronization. Therefore, the program executed after await needs to wait until the function defined by await is executed before it is executed. It needs to wait in the microtask queue.

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('setTimeout')
}, 0)
 
async1();
 
new Promise (function (resolve) {
  console.log('promise1')
  resolve();
}).then (function () {
  console.log('promise2')
})
 
console.log('script end')


结果
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout

The function will only be pushed into the call stack when it is called, so the first thing to execute is console.log, which outputs script start, and then the setTimeout function ("setTimeout") is put into the macro task queue to wait, calls the async1 function, and outputs async1 start, execute the async2 function, output async2, ("async1 end") is placed in the microtask queue and wait, continue to execute the Promise function, output promise1, and ("promise2") in the then function is placed in the microtask queue and wait. Output script end.

The programs in the call stack have all been executed. At this time, the programs in the microtask queue begin to be executed, and async1 end and promise2 are output in sequence.

The program in the microtask queue has also been executed, and the program in the macrotask starts to be executed, and setTimeout is output.

A simple diagram is as follows

It doesn’t matter if you get the question wrong. Just remember the following three sentences and you won’t make any mistakes again.

To judge the execution order, you can remember the following key points:

1. The callback function in promise is executed immediately, and the callback function in then will be pushed into the microtask queue and will be executed after all tasks in the call stack are executed.

2. The content in the async function is put into the call stack for execution, and the next line of await is put into the microtask for execution.

3. After the call stack execution is completed, the microtask queue will be continuously polled. Even if the macrotask is pushed into the queue first, the microtask will be executed first.

Guess you like

Origin blog.csdn.net/ljy_1024/article/details/121487730