Understand microtasks and macrotasks in JavaScript
If a piece of JavaScript code contains setTimeout
, almost everyone knows that the code will be delayed (asynchronously), but if the code contains setTimeout
, await
and Promise resolve
at the same time, then can you still the right order of execution?
This is a front-end interview question on the Internet. The main knowledge point to be examined is the execution sequence of asynchronous async/await
, setTimeout
, Promise resolve
, which is the microtaks
and microtaks
we are going to discuss here:
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("promise end");
});
console.log("script end");
If you want to fully answer this question, you need to understand JavaScript's synchronous queue and asynchronous queue, i.e. the subdivided microtasks and macrotasks in the asynchronous queue.
Synchronous Asynchronous
We know that synchronous tasks will be executed first during the running process of JavaScript. If asynchronous code is encountered, the asynchronous code will be placed in the asynchronous queue, and the code in the asynchronous queue will be executed after the execution of the synchronous code is completed. For example the following code:
console.log(1)
setTimeout(()=>{
console.log(2)
}, 0)
console.log(3)
setTimeout(()=>{
console.log(4)
}, 0)
console.log(5)
The code first executes console.log(1)
on the first line. Continue to execute and encounter the first asynchronous code setTimeout
, at which point JavaScript will place the fragment code in the asynchronous queue (macrotasks
to be exact, which will be discussed later), and then continue to execute the synchronous code console .log(3)
, and then encounters a second setTimeout
, which is also placed in the asynchronous queue and continues to execute the code console.log(5)
. When all the synchronous codes are executed, go back and check whether there are any unexecuted tasks in the asynchronous tasks. If so, execute them in order, which is the setTimeout
code that prints 2 and 4 respectively.
So the result of executing the above code is:
1
3
4
undefined
2
4
Priority in async: microtasks & macrotasks
First look at the following code:
console.log(1)
setTimeout(() => {
console.log(2)
}, 0);
new Promise(function(resolve) {
console.log("3");
resolve();
}).then(function() {
console.log("4");
});
console.log(5)
You must know that the synchronous code console.log(1)
and console.log(4)
will be executed first, but for the two asynchronous Promise.then
and setTimeout
, who will execute first?
Here we want to introduce the concepts of microtasks
and macrotasks
. Both microtasks
and macrotasks
are asynchronous tasks, but the difference is:
* microtasks
tasks take precedence over macrotasks
tasks, which means that when the synchronization task is completed, the tasks in microtasks
will be executed first, and then the tasks in macrotasks
will be executed.
* The execution of microtasks will take precedence over UI tasks (in fact, UI tasks belong to macrotasks
), which means that if you keep inserting tasks into microtasks
, the page will stop responding.
* Common microtasks
are: process.nextTick
, Promises
(where the Promises
constructor is synchronous), queueMicrotask
, MutationObserver
* Common macrotasks
are: setTimeout
, setInterval
, setImmediate
, requestAnimationFrame
, I/O
, UI rendering
So for the code above:
- First execute the synchronous code
console.log(1)
- Encounter
setTimeout
and put it inmacrotasks
- Encountered
Promise
executes the synchronous constructorconsole.log("3"); resolve()
(Promise's constructor is synchronously executed! Promise's constructor is synchronously executed! Promise's construction The function is executed synchronously!) and putthen
intomicrostaks
- Continue to execute the synchronous code
console.log(5)
- When the synchronous code is executed, execute the task in
microstaks
, which isconsole.log("4")
inPromise.then
- After the execution of
microstaks
, execute the task inmacrostaks
, namelyconsole.log(2)
ofsetTimeout
So the result is
javascript
1
3
5
4
undefined
2
Execution order of async and await fragments in asynchrony
Let's move on to the example:
async function async1(){
console.log(2)
await async2()
console.log(3)
}
async function async2(){
console.log(4)
}
console.log(1)
async1()
console.log(5)
Promise.resolve().then(() => console.log(6))
console.log(7)
Hmmm, let me guess:
- First is the sync code
console.log(1)
- Then the sync code in async1
console.log(2)
- followed by the sync code in async2
console.log(4)
But how to deal with this await
? What about the code behind await
?
In fact, the async
function returns a Promise object. When the function is executed, once it encounters await, it will execute the method after await first, and then put all the following code into microtasks
and continue to execute Synchronous tasks, so here will put the following console.log(3)
into microtasks
after executing await async2()
in async1
.
Continue is:
* Put console.log(3)
into microtasks
, jump out of async1
and continue to execute the synchronous code console.log(5)
* When a Promise is encountered, synchronously execute the Promise's constructor, where the Promise constructor is empty.
* Put the code in then
of Promise
into microtasks
* Continue to execute synchronous code console.log(7)
* After the synchronous code is executed, start executing the code in microtasks
, the order is console.log(3)
in async1
, console.log(6)
in Promise.then
So the result is:
javascript
1
2
4
5
7
3
6
Well, the relevant concepts are explained, let's go back and look at the famous interview question:
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("promise end");
});
console.log("script end");
Here is the result!
- First execute the synchronous code
console.log("script start")
- Encounter
setTimeout
and put it in the macrotasks macro task - Continue to execute
console.log("async1 start")
in synchronous codeasync1
- Execute
console.log("async2");
in async2 synchronously, and put the code afterawait
into microtasks - Encounter
Promise
, execute its constructor synchronouslyconsole.log("promise1");
and put thethen
function afterresmove
intomicrotasks
- Continue to synchronously execute the
console.log("script end")
in the last line, the synchronization task is completed, check whether there is a task inmicrotasks
, and execute it - Execute the content after the first task
async1 await
inmicrotasks
console.log("async1 end");
- Execute the next task of
microtasks
,console.log("promise end");
inPromise resolve
- After
microtasks
is executed, check whether there are tasks inmacrotasks
- Execute the
setTimeout
callback inmacrotasks
console.log("setTimeout")
So the result is:
script start
async1 start
async2
promise1
script end
async1 end
promise end
undefined
setTimeout