preface
In the case of Javascript asyncrony, I started at the level of concurrent programming in other object-oriented programming languages without going into too much detail. As a supplement, I was at a loss for a while when choosing a theme. I decided to start with microtasks and macrotasks, and try to get as much of a Promise base as POSSIBLE as I read the rest of the article.
Let’s go.
Event loop
First, a few more details about the last event loop:
- Event loop open
- Sets the new message sequence to the current message sequence
- Fetching the task message sequence from the current message sequence is a first-in, first-out structure, that is, it is fetched in order.
- If you make an asynchronous request and then save the message to the new message sequence (if not, create a new message sequence), all the tasks of the new message sequence are blocked, waiting for the next iteration of the event loop.
- Check whether the current message sequence is empty, if so continue, otherwise go to (3)
- Whether UI Rendering event is triggered, if yes, view Rendering is performed immediately. Otherwise, continue to
- Whether to add a message sequence, if so, start the next event loop, return to (2) otherwise continue
- Ensure that there are no more events and close the event loop. The thread goes to sleep. Until an event occurs, create a new message sequence and save the message, go to (1).
From the above process, the following conclusions can be drawn:
- This event loop subdivides the message sequence into current and new, which are not the same.
- An event loop that processes a sequence of messages, not just a message.
- An event loop is rendered only once.
- This event loop still needs improvement
Macro task
We often talk about tasks, are Macrotasks (Macrotask), by the message sequence composed of Macrotasks, called Macrotask sequence, namely Macrotasks (shuwa suspect determination……) , are generally involved in IO operations (including network requests, page rendering, etc.), such as:
- Scripts: script code
- Mouse/Key Events: Click, onload, input, etc.
- Timers: Timer, for example
setTimeout
setInterval
And so on. - To be continued
Note that Timers work like this:
- call
setTimeout
When, the message (the callback function, i.etask) on theDelayed message queueIn the - After the task in the delay message queue expires, it is put into a new MacroTask.
- Wait for processing in the next iteration of the event loop.
Example: Start with a function that generates a timer.
<div id="text"></div>
Copy the code
var text = document.querySelector('#text')
var genTimer = (string,delay=0,cb) = >{
return (a)= >{
setTimeout((a)= >{
text.innerHTML+=string+'<br/>'! cb||cb(); },delay*1000)}}Copy the code
Now always remember that the script code and Timer is a macro task. And this function will be used until the end. (…… The above code should be easy to understand…)
Event loops and rendering
The task of preparing two message sequences is to simulate a sequence of two event cycles.
var macroTasks = []
macroTasks[0] = [genTimer('A1: Be honest rather clever.'.1),
genTimer('A2: Being on sea, sail; being on land, settle.'.1)]
// The expiration time of the two queues is inconsistent!
macroTasks[1] = [genTimer('B1: Failure is the mother of success.'.3),
genTimer('B2: The shortest answer is doing.'.3)]
/* macroTask[0] is the task for the next message sequence */
Copy the code
Note that tasks in macroTasks[0] all expire after 1s. So these tasks are handled in the next event loop. Similarly, tasks in macroTasks[1] expire after 3s, so they will be processed in the next event loop.
Why pay attention to these differences? Because they are handled in two event cycles!
text.innerHTML +='start......
'
macroTasks[0].forEach(f= >f())
macroTasks[1].forEach(f= >f())
text.innerHTML +='end......
'
Copy the code
The output is as follows:
A1
A2
B1
B2
The key
The instructions are as follows:
- An event loop that processes only one sequence of messages. Due to the
A1
andA2
theTimer
They expire at the same time, so they’re divided into the sameMessage sequence, and an event loop onlyRender a, soA1
andA2
Simultaneously rendered. - In the same way,
B1
andB2
It’s the same thing; But be warned:B1
andB2
The expiration time andA1
andA2
So they are divided into two event cycles.
The basic process is as follows:
To sum up, we can know that:
Tasks in the same message sequence share the same event loop and wait for all tasks to complete before rendering
Why do we come to this conclusion? Go ahead.
congestion
Most of the time, blocking is not perceived, partly because of the computing power of the CPU and partly because of the high performance of the JS engine.
There are occasional exceptions, and in fact, the macro tasks we’re talking about are mostly heavy tasks, such as our JS code file (at least 2000 lines of code), which can easily block if not handled properly.
Now simulate a blocking task, for example:
var macroTasks =[]
// In the next event loop, add a normal task,
macroTasks.push(genTimer('01:Better to light one candle \ than to curse the darkness.'.0))
// Add a blocking task for an excessively long event.
// The blocking duration is 3s
macroTasks.push(genTimer('XXXXXblocking long time! '.0, () = > {console.log('i am running! ');
let c = Date.now();
while((Date.now()-c)<=3000);
}))
text.innerHTML +='hello<br>'
macroTasks.forEach(f= >f())
text.innerHTML +='byebye!
'
Copy the code
Note: macroTasks all expire at the same time, so they are grouped into the same event loop;
Then output the following (please bear with me) :
In the above example, although everything in the MicroTasks is grouped into a single time loop, they are not output after 0s as we expected. It’s blocking 3s at the same time. Why is that?
Description:
- because
microtask
All tasks in, and only all tasks in, an event loopRender events will occur only when all are processed. - So you know,
microtask[0]
andmicrotask[1]
Render only after it has been processed! But because themicrotask[1]
producedblockingAnd eventually resulted incaton.
Therefore, the following conclusions can be drawn:
- If there is one in the message sequence
task
Trapped, the entire event loop will be trappedblockingEventually lead tocaton. In fact, once the event loop is blocked, it will affect the next event loop.
Next, consider macrotasks[1] as blocking tasks that we are completely unaware of.
The above code is always Hello after ByeBye!! The content is not output at all, which makes no sense. So for the sake of user experience, the code looks like this:
var macroTasks =[]
macroTasks.push(genTimer('XXXXXblocking long time! '.0.5, () = > {console.log('i am running! ');
let c = Date.now();
while((Date.now()-c)<=3000);
}))
macroTasks.push(genTimer('01:Be honest rather clever'.1))
macroTasks.push(genTimer('ByeBye'.1))
text.innerHTML +='hello<br>'
macroTasks.forEach(f= >f())
Copy the code
Note: In the code above, macroTasks [0] and MacroTasks [1] and MacroTasks [2] have different event loops that have been staggered. But it was still stuck to 3s before output. The reason is simple, because the current event loop is still being processed, the time to enter the next event loop is delayed.
So here’s the golden rule for all asynchronous models: Never block an event loop. Not only does it cause serious lag, but it also severely impacts the user experience, and more importantly, event loop blocking means a higher performance overhead.
Therefore, we can only process all tasks before blocking tasks, but it is still inevitable to be affected by it in general. For example, when the delay time of blocking tasks is 0s, any macro tasks will be affected by blocking, which annoys users as the first step and makes them gag
Microtasks are one of these solutions (of course the most straightforward solution is to Pass blocking tasks, but in most cases they are important).
Use microtasks
Microtasks are simply tasks that can be done quickly and ensure that all tasks are completed (but still before UI Rendering). In the ES8 specification, microtasks are referred to as jobs, but microtask is more popular, and both terms mean the same thing.
The most commonly used microtask is Promise.
For example:
var macroTasks =[]
macroTasks.push(genTimer('XXXXXblocking long time! '.0, () = > {console.log('i am running! ');
let c = Date.now();
while((Date.now()-c)<=3000);
}))
macroTasks.push((a)= >{
return new Promise(res= >{
text.innerHTML+='lark in the clear air<br/>'
res('success! ');
})
})
macroTasks.push((a)= >{
return new Promise(res= >{
text.innerHTML+='ByeByeBye!
'
})
})
text.innerHTML +='hello<br>'
macroTasks.forEach(f= >f())
Copy the code
Output:
The process diagram is as follows:
macroTasks
Micro tasks
It’s still blocked, but at least there’s nothing on the surface. Of course, this is just an experiment; Do not do this under any circumstances in a production environment. I will not repeat it here.
There are three microtasks in the browser environment:
- QueueMicrotask: microtask callback function.
- I Promise you, I Promise you
- MutationObserver: DOM tree listener
In addition to Promise, these are not commonly used, so if you are interested, you can go to see them. But the microtask feels like synchronous code that can be appended to a macro task. It doesn’t matter what the microtask definition is, it’s important that the microtask be as small as possible, and don’t try to block the microtask or you lose the meaning of the microtask.
Rewrite the above code further:
var text = document.querySelector('#text')
function genMicrotask(str){
return async ()=>{
text.innerHTML += str + '<br>'
returnstr ; }}var microTasks = [genMicrotask('01: For man is man and master of his fate.'),
genMicrotask('ByeBye!!! ')]
text.innerHTML='hello!!!
'
microTasks.forEach(f= >f())
Copy the code
Output:
hello!!!
01: For man is man and master of his fate.
ByeBye!!!
Copy the code
It’s perfect. At least it looks fresher than the last one. Note: The async function also ends up returning a Settled Promise.
Okay, so that’s all for microtasks and macrotasks. (? Promise, I believe that the audience is not zero-based, always know how to use it…)
Update the event loop model
Based on all of the above, add macroTasks and microTasks, which is:
- Event loop open
- Set the new MacroTask to the current Macrotask
- Retrieves the tasks sequentially from the current Macrotasks
- The processing task runs JS code from top to bottom if an asynchronous request is made and then the message is saved toNew Macrotasks(If not, create a new); If there isMicrotasks, then:
- Pull the task from Microtasks
- Run the code
- If an asynchronous request is made, then save the message to a new MacroTask (if none is created)
- If a Microtask exists, it is still added to the Microtasks of the current MacroTask.
- Check whether the current Macrotasks are empty, if yes, continue, otherwise go to (3)
- Whether UI Rendering event is triggered, if yes, view Rendering is performed immediately. Otherwise, continue to
- If MacroTasks are added, if so, start the next event loop and return to (2); Otherwise continue.
- Ensure that there are no more events and close the event loop. The thread goes to sleep. Until an event occurs, create a new message sequence and save the message, go to (1).
In fact, the cycle of events can be simply described as:
- Any event loop is based onMacrotask1 –> MicroTasks –> Macrotask2 –> UI RenderingSequential.
- Any additionsMicrotaskWill only be saved in the current processMacrotask 的 MicrotasksIn the.
- Any additionsMacrotaskIt will only be saved inNew MacrotasksIn, it is for the next event cycleMacrotasks.
The last
I thought I could bring in NodeJS, but the underlying details of NodeJS are far more complex than the Javascript event loop model can capture. We’ll make arrangements for the next chapter. Anyway, the event loop in the document is enough without being nitpicky.
I can only say so much for lack of space… However, this part involves a great deal of knowledge, there are fallacies, but also generous correction, thank you very much.