当前位置:网站首页>[JS] macro task micro task

[JS] macro task micro task

2020-11-10 12:04:45 Zhang Changchang

First ,JavaScript Is a single threaded scripting language .

So, in a single line of code execution , There must not be another line of code that will execute at the same time , It's like using alert() Go crazy later console.log, If you don't close the marquee , The console doesn't show a bar log The information of .

Or some code does a lot of computation , For example, in the front-end brute force crack password and other ghost operations , This will cause the subsequent code to wait , The page is in a fake state , Because the previous code is not finished .

So if all the code is executed synchronously , This can cause serious problems , For example, we need to get some data from the remote end , Is it necessary to loop the code all the time to determine whether or not to get the return result ?_ It's like ordering food in a restaurant , Certainly can't say that after ordering, go to the kitchen to urge people to cook , You're going to get beaten ._

So there's the concept of asynchronous events , Register a callback function , For example, send a network request , We told the main program to wait until it received the data and let me know , Then we can do something else .

And then, after asynchronous completion , We will be informed of , But at this point, maybe the program is doing something else , So even if it's done asynchronously, you need to wait , Wait until the program is idle to see which asynchronies have been completed , You can do it .

Like a taxi , If the driver arrives first , But you still have something to deal with , At this time, it is impossible for the driver to drive his car first , You have to wait until you're done and get in the car .

The difference between a micro task and a macro task

It's like going to a bank to do business , First, take the number and arrange the number .
It's usually printed like :“ Your number is XX, There's still XX people .” Words like that .

Because the teller is also responsible for dealing with a customer who comes to handle business , At this time, each person to handle business can be considered as a bank teller's macro task to exist , When the teller has finished dealing with the current customer's problem , Choose to receive the next one , Radio number , That's the start of the next macro task .
So when multiple macro tasks are put together, we can say that there is a task queue here , Inside are all the customers in the current bank .
The task queue is full of completed asynchronous operations , Instead of registering an asynchronous task, it will be placed in the task queue , It's like queuing in a bank , If you're not there when you're called , Then your current number plate is invalid , The teller will choose to skip the business processing of the next customer directly , When you get back, you need to pick up the number again

And a macro task in the process of execution , You can add some micro tasks , It's like doing business at the counter , An old man in front of you may be saving money , After the deposit business is completed , The teller will ask the old man if he has any other business to handle , Then the old man thought for a moment :“ lately P2P There's a little bit of thunder , Do you want to choose a more stable financial management ”, Then tell the teller that , We need to do some financial services , At this time, the teller can't tell the old man that :“ You go up to the back and take a number , The end of the line ”.
So it's your turn to do business , Because the old man added it temporarily “ Financial management business ” And push back .
Maybe the old man still wants to Get another credit card ? perhaps Buy some more commemorative coins
Whatever the need , As long as the teller can handle it for her , Will do these things before you deal with your business , These are all micro tasks .

This means that : Your uncle will always be your uncle
When the current micro task is not completed , Will not perform the next macro task .

So there's the question that often appears in the interview 、 Code snippets from various blogs :

setTimeout(_ => console.log(4))

new Promise(resolve => {
  resolve()
  console.log(1)
}).then(_ => {
  console.log(3)
})

console.log(2)
 Copy code 

setTimeout It exists as a macro task , and Promise.then They are representative micro tasks , The execution order of the above code is output according to the sequence number .

All incoming asynchronies refer to the part of the code in the event callback
in other words new Promise The code is executed synchronously during the instantiation process , and then The callback registered in is executed asynchronously .
Go back to check whether there is an asynchronous task after the synchronous code is executed , And execute the corresponding callback , Micro tasks are executed before macro tasks .
So we get the above output conclusion 1、2、3、4.

+ Part of the code that represents synchronous execution

+setTimeout(_ => {
-  console.log(4)
+})

+new Promise(resolve => {
+  resolve()
+  console.log(1)
+}).then(_ => {
-  console.log(3)
+})

+console.log(2)
 Copy code 

Originally setTimeout The timer has been set first ( It's equivalent to taking a number ), Then add some more to the current process Promise To deal with ( Add business temporarily ).

So advanced , Even if we continue to Promise Instantiation in Promise, Its output will still be earlier than setTimeout The macro task of :

setTimeout(_ => console.log(4))

new Promise(resolve => {
  resolve()
  console.log(1)
}).then(_ => {
  console.log(3)
  Promise.resolve().then(_ => {
    console.log('before timeout')
  }).then(_ => {
    Promise.resolve().then(_ => {
      console.log('also before timeout')
    })
  })
})

console.log(2)
 Copy code 

Yes, of course , In fact, there are few simple calls like this Promise Of , Generally, there are other asynchronous operations in it , such as fetchfs.readFile Something like that .
And these are actually equivalent to registering a macro task , Not micro tasks .

P.S. stay Promise/A+ The specification of in ,Promise The implementation of can be a micro task , It can also be a macro task , But the general consensus is that ( At least Chrome That's what it does ),Promise It should belong to the micro task camp

therefore , Understand which operations are macro tasks 、 What are micro tasks becomes critical , This is a popular saying in the industry at present :

Macro task

browser

Node

I/O

setTimeout

setInterval

setImmediate

requestAnimationFrame

Some places will be listed UI Rendering, This is also a macro task , But I'm reading HTML Specification document in the future , It is obvious that this is an operation step parallel to the micro task
requestAnimationFrame Let's call it a macro task ,requestAnimationFrame stay MDN The definition of by , What to do before the next page redraw , Redrawing also exists as a step in a macro task , And this step is later than the execution of the micro task

Micro task

browser

Node

process.nextTick

MutationObserver

Promise.then catch finally

Event-Loop What is a

There has been a discussion about Macro task 、 Micro task , The execution of various tasks .
But back to reality ,JavaScript It's a single process language , Can't handle multiple tasks at the same time , So when do macro tasks , When to perform a micro task ? We need to have such a logic of judgment .

Every time a business is completed , The teller will ask the current customer , Is there any other business that needs to be handled ._( Check if there are any micro tasks to deal with )_
And the customer made it clear that there was nothing later , The teller went to check if there were any people waiting for business ._( End this macro task 、 Check if there are any macro tasks to be processed )_
The process of this inspection is ongoing , Every time you finish a task, you do it once , And this kind of operation is called Event Loop._( This is a very simple description of , It's actually going to be a lot more complicated )_

And it's like that , A teller can only handle one thing at a time , Even if these things are raised by a client , So we can think that there is a queue for micro tasks , It's basically a logic like this :

const macroTaskList = [
  ['task1'],
  ['task2', 'task3'],
  ['task4'],
]

for (let macroIndex = 0; macroIndex < macroTaskList.length; macroIndex++) {
  const microTaskList = macroTaskList[macroIndex]
  
  for (let microIndex = 0; microIndex < microTaskList.length; microIndex++) {
    const microTask = microTaskList[microIndex]

    //  Add a micro task 
    if (microIndex === 1) microTaskList.push('special micro task')
    
    //  Perform tasks 
    console.log(microTask)
  }

  //  Add a macro task 
  if (macroIndex === 2) macroTaskList.push(['special macro task'])
}

// > task1
// > task2
// > task3
// > special micro task
// > task4
// > special macro task
 Copy code 

Why use two for Loop to express , It's because it's easy to do it inside the loop push Something like that ( Add some tasks ), So that the number of iterations is increased dynamically .

And to be clear ,Event Loop It's just about telling you what to do , Or which callbacks are triggered , The real logic is still executed in the process .

Representation in the browser

The difference between the two tasks is simply explained above , as well as Event Loop The role of , So what does it look like in a real browser ?
The first thing to be clear is , Macro tasks must be executed after micro tasks ( Because a microtask is actually one of the steps in a macro task )

I/O This one feels a bit general , There are so many things that can be called I/O, Click once button, Upload a file , All of these things that interact with a program can be called I/O.

Suppose there are some of these DOM structure :

<style> #outer {
    padding: 20px;
    background: #616161;
  }

  #inner {
    width: 100px;
    height: 100px;
    background: #757575;
  } </style>
<div id="outer">
  <div id="inner"></div>
</div>
 Copy code 
const $inner = document.querySelector('#inner')
const $outer = document.querySelector('#outer')

function handler () {
  console.log('click') //  Direct output 

  Promise.resolve().then(_ => console.log('promise')) //  Register for micro tasks 

  setTimeout(_ => console.log('timeout')) //  Register macro tasks 

  requestAnimationFrame(_ => console.log('animationFrame')) //  Register macro tasks 

  $outer.setAttribute('data-random', Math.random()) // DOM Attribute changes , Trigger micro task 
}

new MutationObserver(_ => {
  console.log('observer')
}).observe($outer, {
  attributes: true
})

$inner.addEventListener('click', handler)
$outer.addEventListener('click', handler)
 Copy code 

If you click #inner, The order of execution must be :click -> promise -> observer -> click -> promise -> observer -> animationFrame -> animationFrame -> timeout -> timeout.

Because once I/O Created a macro task , That is to say, it will trigger in this mission handler.
According to the comments in the code , After the synchronized code has been executed , At this point, I will check whether there are micro tasks that can be executed , And then I found out Promise and MutationObserver Two micro tasks , So it was carried out .
because click Events will bubble , So the corresponding this time I/O Will trigger twice handler function (_ Once in inner、 Once in outer_), So the bubbling event will be executed first (_ Prior to other macro tasks _), In other words, it will repeat the above logic .
After executing the synchronization code and micro task , At this point, continue to search backward for any wood or macro task .
One thing to note is that , Because we triggered setAttribute, It's actually modified DOM Properties of , This will cause the page to redraw , And this set The operation of is executed synchronously , in other words requestAnimationFrame The callback will be earlier than setTimeout Executed .

Some little surprises

Use the above example code , If you will manually click DOM The trigger mode of the element changes to $inner.click(), Then you get different results .
stay Chrome The output order is roughly like this :
click -> click -> promise -> observer -> promise -> animationFrame -> animationFrame -> timeout -> timeout.

With our manual trigger click The reason why the execution order of is different is that , Because it's not a trigger event implemented by the user by clicking on the element , It's like dispatchEvent The way , Personally, I don't think it's an effective one I/O, It was executed once handler The callback registered the micro task 、 After registering macro tasks , Actually, it's outside $inner.click() It's not finished .
So before the micro task is executed , And continue bubbling for the next event , That is to say, it triggered the second time handler.
So output the second time click, Wait until these two times handler We will check if there are any micro tasks after they have been executed 、 There are no macro tasks .

Two things need to be noted :

  1. .click() In my opinion, this way of triggering events is similar to dispatchEvent, Can be understood as synchronous execution of code
document.body.addEventListener('click', _ => console.log('click'))

document.body.click()
document.body.dispatchEvent(new Event('click'))
console.log('done')

// > click
// > click
// > done
 Copy code 
  1. MutationObserver You don't have to trigger multiple times at the same time , Multiple modifications will only trigger a callback once .
new MutationObserver(_ => {
  console.log('observer')
  //  If you output here DOM Of data-random attribute , Must be the last value , No explanation. 
}).observe(document.body, {
  attributes: true
})

document.body.setAttribute('data-random', Math.random())
document.body.setAttribute('data-random', Math.random())
document.body.setAttribute('data-random', Math.random())

//  Only output once  ovserver
 Copy code 

It's like going to a restaurant to order , The waiter called three times ,XX No. 1 beef noodles , Doesn't mean she'll give you three bowls of beef noodles .
The above point of view is from Tasks, microtasks, queues and schedules, There is an animated version of the explanation

stay Node Performance in

Node It's also a single thread , But it's being dealt with Event Loop It's a little different from the browser , Here is Node Official documents The address of .

Just from API On the level of understanding ,Node Two new methods have been added to use : The task of micro process.nextTick And macro tasks setImmediate.

setImmediate And setTimeout The difference between

Definition in official documents ,setImmediate As a Event Loop After execution, call .
setTimeout It is executed by calculating a delay time .

But it also mentioned that if you execute these two operations directly in the main process , It's hard to guarantee which will trigger first .
Because if two tasks are registered in the main process first , Then it takes more time to execute code than XXs, At this time, the timer is in the state of executable callback .
So the timer will be executed first , And after the timer is executed, it is finished once Event Loop, Only then will the execution be carried out setImmediate.

setTimeout(_ => console.log('setTimeout'))
setImmediate(_ => console.log('setImmediate'))
 Copy code 

If you are interested, you can try it yourself , Doing it multiple times really leads to different results .

But if you add some code later , You can guarantee setTimeout It will be there. setImmediate It triggered :

setTimeout(_ => console.log('setTimeout'))
setImmediate(_ => console.log('setImmediate'))

let countdown = 1e9

while(countdown--) { } //  We make sure that the loop runs faster than the countdown of the timer , That leads to the end of the cycle ,setTimeout The callback is ready to be executed , So it's going to execute first `setTimeout` End the cycle again , That is to say, start implementing `setImmediate`
 Copy code 

If in another macro task , It must be setImmediate Execute first :

require('fs').readFile(__dirname, _ => {
  setTimeout(_ => console.log('timeout'))
  setImmediate(_ => console.log('immediate'))
})

//  If you use a set delay setTimeout The same effect can be achieved 
 Copy code 

process.nextTick

It's like that , This one can be thought of as similar to Promise and MutationObserver Micro task implementation of , You can insert... At any time during code execution nextTick, And it will ensure that it is executed before the next macro task starts .

One of the most common examples of usage is the operation of some event binding classes :

class Lib extends require('events').EventEmitter {
  constructor () {
    super()

    this.emit('init')
  }
}

const lib = new Lib()

lib.on('init', _ => {
  //  It will never be implemented here 
  console.log('init!')
})
 Copy code 

Because the above code is instantiating Lib Object is executed synchronously , After the instantiation is completed, it sends init event .
At this time, the main program in the outer layer has not yet started to execute lib.on('init') This step in monitoring the event .
So there is no callback when sending the event , After the callback is registered, the event will not be sent again .

We can easily use process.nextTick To solve this problem :

class Lib extends require('events').EventEmitter {
  constructor () {
    super()

    process.nextTick(_ => {
      this.emit('init')
    })

    //  In the same way, use other micro tasks 
    //  such as Promise.resolve().then(_ => this.emit('init'))
    //  The same effect can be achieved 
  }
}
 Copy code 

This will be done after the main process code execution , Trigger when the program is idle Event Loop Process to find whether there are micro tasks , And then send init event .

About what is mentioned in some articles , Cycle call process.nextTick It can lead to the police , Subsequent code will never be executed , That's right , See double loop implementation above loop that will do , It's equivalent to every time for During the execution of the loop, the array is push operation , So the cycle never ends

Say more async/await function

because ,async/await In essence, it is based on Promise Some of the packages , and Promise It's a kind of micro task . So it's using await The key words and Promise.then The effect is similar to :

setTimeout(_ => console.log(4))

async function main() {
  console.log(1)
  await Promise.resolve()
  console.log(3)
}

main()

console.log(2)
 Copy code 

async Function in await The previous code was executed synchronously , It can be understood as await The previous code belongs to new Promise Code passed in when ,await All the code after that is in Promise.then Callback in

Section

JavaScript There are many articles written on the Internet , I'm too shallow , Can only simply say their own understanding of it .
I didn't make a document , Step by step , Look at the current stack like what 、 Execute the selected task queue , Various balabala.
I don't think it's very helpful to write code , It's better to go through the door , Sweep a blind eye , Just get a general idea of what it is .

I recommend several articles for reference :

author :Jiasm
link :https://juejin.im/post/684490...
source : Nuggets
The copyright belongs to the author . Commercial reprint please contact the author for authorization , Non-commercial reprint please indicate the source .

版权声明
本文为[Zhang Changchang]所创,转载请带上原文链接,感谢