What is the asynchronous flow of async await setTimeout promise? What is the order?

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
*/

This question mainly examines the order of function execution in the event loop, including async , await, setTimeout, Promisefunctions. Let's talk about the knowledge points involved in this question.

1. Task queue

First we need to understand the following things:

  • JS is divided into synchronous tasks and asynchronous tasks
  • Synchronous tasks are executed on the main thread, forming an execution stack
  • In addition to the main thread, the event trigger thread manages a task queue. As long as the asynchronous task has a running result, an event is placed in the task queue.
  • Once all synchronous tasks in the execution stack are executed (the JS engine is idle at this time), the system will read the task queue, add runnable asynchronous tasks to the executable stack, and start execution.

According to the specification, the event loop is coordinated through the mechanism of task queue . In an Event Loop, there can be one or more task queues (task queue), a task queue is a collection of ordered tasks (tasks); each task has a task source (task source), from the same Tasks from a task source must be put into the same task queue, and tasks from different sources are added to different queues. APIs such as setTimeout/Promise are task sources, and what enters the task queue is the specific execution task they specify.

 

task queue

macro task

(macro)task (also known as a macro task), it can be understood that the code executed by the execution stack each time is a macro task (including each time an event callback is obtained from the event queue and placed in the execution stack for execution).

In order to enable the orderly execution of JS internal (macro) tasks and DOM tasks, the browser will re-render the page after a (macro) task is executed and before the next (macro) task is executed. The process is as follows:

(macro)task->渲染->(macro)task->...

(macro) task mainly includes: script (overall code), setTimeout, setInterval, I/O, UI interaction events, postMessage, MessageChannel, setImmediate (Node.js environment)

micro task

Microtask (also known as microtask), can be understood as a task that is executed immediately after the execution of the current task ends . That is, after the current task, before the next task, and before rendering.

So its response speed will be faster than setTimeout (setTimeout is a task), because there is no need to wait for rendering. That is to say, after a macrotask is executed, all microtasks generated during its execution will be executed (before rendering).

Microtask mainly includes: Promise.then, MutaionObserver, process.nextTick (Node.js environment)

operating mechanism

In the event loop, each cycle operation is called tick, and the task processing model of each tick is relatively complicated, but the key steps are as follows:

  • Execute a macro task (get it from the event queue if it is not in the stack)
  • If a microtask is encountered during execution, it is added to the task queue of the microtask
  • After the macro task is executed, immediately execute all micro tasks in the current micro task queue (executed sequentially)
  • After the current macro task is executed, the rendering starts to be checked, and then the GUI thread takes over the rendering
  • After rendering, the JS thread continues to take over and start the next macro task (obtained from the event queue)

The flow chart is as follows:

mark

Immediate execution in Promise and async

We know that the asynchrony in Promise is reflected in thenand catch, so the code written in Promise is executed immediately as a synchronous task. In async/await, the code in it is executed immediately before await appears. So what happened when await appeared?

what does await do

Literally, await is to wait. What await waits for is an expression. The return value of this expression can be a promise object or other values.

Many people think that await will wait until the subsequent expression is executed before continuing to execute the following code. In fact, await is a sign to give up the thread. The expression after await will be executed first, the code after await will be added to the microtask, and then the entire async function will be jumped out to execute the following code.

 

Because async await itself is the syntactic sugar of promise+generator. So the code behind await is microtask. So for this question

async function async1() {
	console.log('async1 start');
	await async2();
	console.log('async1 end');
}

Equivalent to

async function async1() {
	console.log('async1 start');
	Promise.resolve(async2()).then(() => {
                console.log('async1 end');
        })
}

back to this topic

The above is all the relevant knowledge points involved in this question, let's go back to this question to see what's going on step by step.

  1. First, the event loop starts from the macrotask queue. At this time, there is only one script (whole code) task in the macrotask queue; when a task source is encountered, the task will be distributed to the corresponding task first. go in the queue. Therefore, the first step of the above example is executed as shown in the following figure:

  2. Then we saw that two async functions were defined first, then looked down, and then encountered  console a statement and output it directly  script start. After the output, the script task continues to execute. When encountering  setTimeoutit as a macro task source, it will first distribute its tasks to the corresponding queue:

  3. The script task continues to execute, and the async1() function is executed. As mentioned above, the code before await in the async function is executed immediately, so it will be output immediately async1 start.

    When encountering await, the expression after await will be executed once, so it will be output immediately async2, and then the code after await will be console.log('async1 end')added to the Promise queue in microtask, and then the async1 function will be jumped out to execute the following code.

  4. The script task continues to execute and encounters a Promise instance. Since the functions in Promise are executed immediately, the subsequent  .thenones will be distributed to the microtask  Promise queue. So it will be output first  promise1, then executed  resolve, and will  promise2 be assigned to the corresponding queue.

  5. The script task continues to execute, and only one sentence is output at the end  script end. At this point, the execution of the global task is completed.

    According to the above, after each execution of a macro task, it will check whether there are Microtasks; if so, execute Microtasks until the Microtask Queue is cleared.

    Therefore, after the execution of the script task is completed, it starts to search and clear the microtask queue. At this time, in the microtask,  Promise there are two tasks in the queue async1 end, promise2so they are output in sequence  async1 end,promise2. When all Microtasks are executed, it means that the first round of the cycle is over.

  6. At the beginning of the second cycle, it will jump back to the async1 function to execute the following code, and then encounter a synchronous task  console statement and output it directly  async1 end. This completes the second round of the cycle. (It can also be understood as being added to the script task queue, so it will be executed first with the setTimeout queue)

  7. The second cycle still starts from the macro task queue. At this time, there is only one macro task  setTimeout, just take it out and output it directly, and the whole process ends here.

Variant one

In the first variant, I also turned the function in async2 into a Promise function, the code is as follows:

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    //async2 makes the following changes:
    new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
    });
}
console.log('script start');

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

new Promise(function(resolve) {
    console.log('promise3');
    resolve();
}).then(function() {
    console.log('promise4');
});

console.log('script end');

You can first see for yourself what the output order will be, and the results will be announced below:

script start
async1 start
promise1
promise3
script end
promise2
async1 end
promise4
setTimeout

After the first macrotask is executed, that is, script endafter the output, all microtasks will be cleaned up. So it will output one after another promise2,  async1 end , promise4, and I won’t say more about the rest.

Variant two

In the second variant, I changed the code after await in async1 and the code of async2 to asynchronous, the code is as follows:

async function async1() {
    console.log('async1 start');
    await async2();
    //Change as follows:
    setTimeout(function() {
        console.log('setTimeout1')
    },0)
}
async function async2() {
    //Change as follows:
	setTimeout(function() {
		console.log('setTimeout2')
	},0)
}
console.log('script start');

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

new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');

You can first see for yourself what the output order will be, and the results will be announced below:

script start
async1 start
promise1
script end
promise2
setTimeout3
setTimeout2
setTimeout1

After the output is promise2, it will be output sequentially in the order of adding to the setTimeout queue. Through the code, we can see that the adding order is 3 2 1, so it will be output in the order of 3, 2, 1.

Variant three

Variant 3 is the original question I saw in a face-to-face scripture. Overall, it is similar with minor differences. The code is as follows:

async function a1 () {
    console.log('a1 start')
    await a2()
    console.log('a1 end')
}
async function a2 () {
    console.log('a2')
}

console.log('script start')

setTimeout(() => {
    console.log('setTimeout')
}, 0)

Promise.resolve().then(() => {
    console.log('promise1')
})

a1()

let promise2 = new Promise((resolve) => {
    resolve('promise2.then')
    console.log('promise2')
})

promise2.then((res) => {
    console.log(res)
    Promise.resolve().then(() => {
        console.log('promise3')
    })
})
console.log('script end')

It’s nothing more than doing some articles on micro-tasks. If you understand the previous content, this question must be no problem. The results are as follows:

script start
a1 start
a2
promise2
script end
promise1
a1 end
promise2.then
promise3
setTimeout

Guess you like

Origin blog.csdn.net/guoweifeng0012/article/details/95598455
Recommended