JavaScript is a single-threaded program, that is, it only has one main thread, and all programs are executed line by line “queued”. In this case, there may be some problems. For example, setTimeout and Ajax wait for a long time to execute, which will block the execution of subsequent code, making the entire program take a long time to execute. In order to deal with this problem, JavaScript code has several “channels” when it is executed.

The first is the call stack, which executes short operations, and the longer operations are placed in the task queue, which is divided into macro tasks and micro-tasks. In the micro-task queue, operations such as promise.then, aysnc and await are placed in the queue. SetTimeout, AJAX and onClick events are placed in the macro task queue. When the task execution of the call stack is completed, the micro-task queue will be polled. The macro task will be executed after the task execution in the micro-task queue is completed.

Mentioned here the stack and queue, in a nutshell this two kinds of data structure, the structure of the stack is a last in, first out, can only be accessed from the rear, removed from the tail, take life scenarios to terms, like the buffet dinner plate, the first to put the plate on the bottom, finally put the plate on the top, need to take the top plate one by one, to get the bottom plate.

And the queue is a first-in, first-out structure, entering from the tail and deleting from the head, just like when we go to the queue to buy things, the students who go first can be the first to buy things.

Back to the event loop, programs that do not block the main process are put on the call stack, pushed to the bottom of the stack, and then popped out. If it is a function, then all the contents of the function are popped out, while programs that block the main process are put into the task queue, and they need to “queue” to execute in turn.

Let’s start with a simple example to determine the order in which the following programs are executed

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

Creating an instance of a Promise starts an asynchronous task. The incoming callback function, also called excutor, executes immediately, so enter the Promise and use resolve to return a successful execution. Executes in the then function are pushed to the microtask queue until the call stack completes execution.

To find a function that defines func1, the function is not called at this time and is not pushed to the call stack for execution. The program continues down, finds a call to the setTimeout function, pushes the printed setTimeout into the macro task queue, then executes the call function func1, pushes func1 into the call stack, executes func1, and at this time outputs fun1.

When everything in the call stack is finished, start polling the microtask queue, enter the then callback 5, and finally execute the macro task queue and enter setTimeout

Let’s do a more complicated 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");
});

The callback function executed by setTimeout (“set1″) is placed directly on the macro task queue to wait. Promise’s excutor function is executed immediately by typing pr1 first, and Promise. Then function (” TheN1 “) is placed on the microtask queue to wait. The following setTimeout callback function (“set2”) is also placed in the macro task queue, after (“set1″), and the call stack outputs 2. QueueMicroTask indicates that a microtask is started, which is the same as the Promise. Then function. (” QueueMicrotask1 “) is put into the microtask queue and executed further down, the new Promise excutor function is executed immediately, and the then function (” TheN3 “) is put into the microtask queue and waited, at which point the call stack is typed in pr1, 2 in turn.

When the program in the call stack has finished executing, go to the microtask queue to execute the microtask, and output then1, queueMicrotask1, then3 in sequence.

When the tasks in the microtask queue are completed, print set1, execute Promise’s excutor function, and resolve returns a successful execution result. Then function (“then2”) is put into the microtask. Once there are tasks in the microtask queue, macro tasks will not be executed in the future. So another setTimeout function in the macro task queue (“set2″) doesn’t execute at this point, goes to the microtask queue to execute (” theN2 “), prints theN2, executes a Promise function, (” theN4 “) is placed on the microtask queue, prints theN4.

The microtask queue has also been completed. At this point, enter the macro task queue and execute set2.

So the final output is:

pr1
2
then1
queueMicrotask1
then3
set1
then2
then4
set2

The simple diagram is as follows

And then the last one, async, await

One conclusion to start with is that async functions are executed on the call stack. “await” turns an asynchronous program into a synchronous one. So a program that executes after “await” will wait until the function defined by “await” completes and will 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')

Functions are only pushed to the call stack when called, so the first thing to execute is console.log, which prints script start, and then the setTimeout function (“setTimeout”) is put into the macro task queue and waits, calling async1, Output async1 start, execute async2 function, output async2, (“async1 end”) put into the microtask queue and wait, then execute the Promise function, output promise1, then (“promise2”) put into the microtask queue and wait, Print Script End.

After all the programs in the call stack have finished executing, start executing the programs in the microtask queue and output async1 end and promise2 in turn.

The program in the microtask queue has also finished executing. Start executing the program in the macro task, and print setTimeout.

The output order is

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

The simple diagram is as follows

Here are a few key points to keep in mind when judging the order of execution

Callbacks in Promise are immediately executed, and callbacks in Then are pushed to the microtask queue and wait for all tasks on the call stack to execute

2. The contents of the async function are placed on the call stack for execution. The contents of the next line of the await function are placed on the microtask for execution

3. After the execution of the call stack is completed, the microtask queue will be constantly polled. Even if the macro task is pushed to the queue first, the microtask will be executed first