Detailed explanation of macro task and micro task AND interview questions

First of all, let’s take a look at the interview questions that were all the rage in 19 years. Everyone should have seen it.

console.log('script start')

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

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

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

console.log('script end')

Everyone should be familiar with this interview question. Today, I took a look at this interview question in a leisurely manner, and then looked at the simple principles. If it is not written in place, I hope the boss will give you pointers.
Main process
1. async await
2. Macro task and micro task
3. Brief explanation of this question

First, let’s talk about async await

async await is the product of ES7 in order to solve the promise callback hell, and make the code clearer and easier to understand.
async is the syntactic sugar of the Generator function, and the Generator function has been improved.

Introduction to Generator function

Generators are very similar to functions and are defined as follows:

//主要关注点:
//1.*星号
//2.yield
function* foo(x) {
    
    
    yield x + 1;
    yield x + 2;
    return x + 3;
}

When we execute the above function

hw.next()// { value: 'hello', done: false }
hw.next()// { value: 'world', done: false }
hw.next()// { value: 'ending', done: true }
hw.next()// { value: undefined, done: true }

It can be seen from the results that the Generator function will not be executed when it is called. It will only be executed when the next method is called and the internal pointer points to the statement. That is, the function can be paused or resumed. Every time the next method of the iterator object is called, an object with two attributes value and done will be returned. The value attribute represents the value of the current internal state, which is the value of the expression following the yield expression; the done attribute is a Boolean value, which indicates whether the traversal is over.

Generator is a container for asynchronous operations. Its automatic execution requires a mechanism, when the asynchronous operation has a result, the execution right can be automatically returned. There are two ways to do this (related to the macro tasks and micro tasks we will talk about next):

1. Callback function. Wrap the asynchronous operation into a Thunk function, and return the execution right in the callback function.
2. Promise object. Wrap the asynchronous operation into a Promise object, and use the then method to return the right of execution.

A simple automatic executor based on the Promise object:
when we use the generator function to parse the above function, the analysis is as follows

function* foo(x) {
    let response1 = yield fetch(x+1) //返回promise对象
    console.log('response1')
    console.log(response1)
    let response2 = yield fetch(x+2) //返回promise对象
    console.log('response2')
    console.log(response2)
}
run(foo);

async/await

ES7 introduced async/await, which can completely bid farewell to executors and generators, and achieve more intuitive and concise code. According to the MDN definition, async is a function that is executed asynchronously and implicitly returns a Promise as a result. It can be said that async is the syntactic sugar of the Generator function, and the Generator function has been improved.

The code in the previous article is implemented with async like this:

const foo = async () => {
    
    
    let response1 = await fetch(x+1) 
    console.log('response1')
    console.log(response1)
    let response2 = await fetch(x+2) 
    console.log('response2')
    console.log(response2)
}

A comparison will reveal that the async function is to replace the asterisk (*) of the Generator function with async, and replace the yield with await (this is the reason for the two points of concern I mentioned above).

At this time, some people may say: What is the difference between this execution method and Generator, this is just a change in writing

The improvement of the Generator function by the async function is reflected in the following four points:

1. Built-in actuator. The execution of the Generator function must rely on the executor, and the async function has its own executor, without the need to manually execute the next() method.

2. Better semantics. Async and await have clearer semantics than asterisk and yield. async indicates that there is an asynchronous operation in the function, and await indicates that the following expression needs to wait for the result.

3. Broader applicability. The co module agrees that the yield command can only be followed by the Thunk function or the Promise object, and the await command of the async function can be followed by the Promise object and the value of the primitive type (number, string, and Boolean value, but it will automatically be converted to immediate resolved Promise object).

4. The return value is Promise. The return value of the async function is a Promise object, which is more convenient than the Iterator object returned by the Generator function. It can be called directly using the then() method.

Macro tasks and micro tasks and their execution order

When it comes to macro tasks and micro tasks, we have to mention Event Loop.
The essence of JS is a single line:

  1. Generally speaking, non-blocking tasks are synchronized and completed directly on the execution stack of the main thread.
  2. Generally speaking, blocking tasks will be executed asynchronously, and asynchronous work will generally be handed over to other threads for completion, and then the callback function will be placed in the event queue.

When the task of the main thread is executed (the execution stack is empty), JS will ask the event queue

Execute a macro task (execute the synchronization code first) -> execute all micro tasks -> UI render -> execute the next macro task -> execute all micro tasks -> UI render ->...

According to the HTML Standard, after one round of event loop execution ends, UI render will start before the next round of event loop execution. That is: after the macro-task task is executed, and then all the micro-task tasks are executed, the current round of the loop ends and the UI render is executed. After the UI render is completed, the next round of loop follows. But UI render may not be executed, because the performance consumed by UI rendering needs to be considered whether there has been any UI changes

Macro task

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

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

宏任务包含:
script(整体代码)
setTimeout
setInterval
I/O
UI交互事件
postMessage
MessageChannel
setImmediate(Node.js 环境)

Micro task

Microtask can be understood as a task that is executed immediately after the execution of the current task ends. In other words, after the current task, before the next task, before rendering.

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

微任务包含:
Promise.then
Object.observe
MutaionObserver
process.nextTick(Node.js 环境)

When we finish reading these, we will come back to the above question

console.log('script start')

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

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

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

console.log('script end')
// script start => async2 end => Promise => script end => promise1 => promise2 => async1 end => setTimeout

Analyze this code:

1. Execute the code and output script start.

2. Execute async1(), call async2(), and output async2 end. At this time, the context of the async1 function will be retained, and then the async1 function will be jumped out.

3. When setTimeout is encountered, a macro task is generated

4. Execute Promise and output Promise. When encountering then, generate the first micro task

5. Continue to execute the code and output script end

6. After the code logic is executed (execution of the current macro task is completed), start to execute the micro task queue generated by the current macro task, and output promise1. When the micro task encounters then, a new micro task is generated

7. Execute the generated micro task, output promise2, and the current micro task queue is executed. Execution right back to async1

8. Executing await will actually produce a promise return, namely

9.let promise_ = new Promise((resolve,reject){ resolve(undefined)}) The
execution is complete, execute the statement after await, and output async1 end

10. Finally, execute the next macro task, that is, execute setTimeout and output setTimeout

Guess you like

Origin blog.csdn.net/lbchenxy/article/details/109382124