Welcome to “Knock the Interviewer” series, Blue will share with you all kinds of knowledge and “pits” in the interview series, welcome to pay attention to me, the wonderful content will not miss, if you meet the topic you are interested in and want to discuss, welcome to let me know through the message, thank you, but please remember:
Interviews can be handled, but don’t fool yourself, thorough knowledge is the key to promotion ———— Blue said
The concepts of Event Loop, macro task and micro task are frequently asked in recent interviews. Understanding it can not only help us better deal with the interview, but also help us thoroughly understand the execution mechanism of JS and write more efficient code. This article will help you thoroughly understand the Event Loop, macro task and micro task. Welcome to like, favorites, comment, forward
Outline of the content
- What is the event loop, the internal execution mechanism of JS code
- What are macro tasks and micro tasks
- The pit of async
- Summary, update & supplement
What is an event loop?
In JS, we often need to “simultaneously” multiple tasks, such as timers, events, asynchronous data interaction, etc., so how does JS manage these tasks, and how to determine their execution sequence?
First of all, all languages have the concept of concurrency model, that is, how multiple tasks can be executed at the same time. Most languages support multi-threading. JS has the simplest concurrency model of all languages
We use schematic code to represent the JS event loop
while(Get task ()){execute task (); }Copy the code
To put it simply, the js event loop reads a task each time, and then executes the task. After executing the task, it continues to obtain the next task. If there is no task, the execution will be suspended and wait for the next task to arrive. If a new task arrives during the execution of the task, the execution of the existing task is not interrupted, but added to the end of the queue to wait
In conclusion, JS uses single-threaded execution based on an event loop, and is executed non-interruptively (that is, the current task will be executed no matter what happens, and no other task will be executed in the middle of the execution), which, you might wonder, is very poor performance. Indeed, let’s look at the pros and cons of doing so
Multithreading (C, Java, etc.) | Single-threaded event drivers (JavaScript) | |
---|---|---|
complexity | High complexity and hair-consuming issues such as synchronization between threads | Simple and easy to use, there will never be a problem of resource competition |
performance | CPU performance is high, suitable for computationally intensive tasks | A single thread does not provide maximum CPU performance (supplemented by webWorker), but front-end applications are not computationally intensive by nature |
blocking | It does not block and large tasks can be handled by a single open thread | It doesn’t actually block because THE IO tasks in JS are asynchronous (files, network), and while large computing tasks still block UI threads, this is rarely the case for the front end |
Therefore, JS single thread event loop is actually very suitable for the front-end use, greatly simplifies the complexity of the program, at the same time, the front-end will have less large computing tasks, so the performance is not a problem
Bottom line: Single-threaded event loops may seem “a little low,” but they’re perfect for front-end development
The concept of task queues
Now that you understand the concept of an event loop, let’s move on to a task queue, which is essentially an array of tasks to be processed
Whenever we want to execute a new task (such as a timer), we add a task to the end of the queue. When the current task completes, the event loop goes to the head of the queue to find the next executable task. Let’s use an example to better understand this
Example: Task queues and timers
console.log('aaaa');
setTimeout(() = >{
console.log('cccc');
}, 0); // This 0 milliseconds is important
console.log('bbbb');
Copy the code
Using the idea of a task queue, we can analyze the execution of this program:
- Step 1: It executes first
console.log('aaaa')
, very common synchronization code - Step 2: Timers are key to this, especially 0 milliseconds
- 0 ms means no delay, so console.log(‘ CCCC ‘) should have been executed directly, but…
- The timer is not executed immediately because it cannot interrupt the current task (JS is not executed in a snap), so it can only be placed at the end of the queue
- Step 3: Execute
console.log('bbb')
- Step 4: The current task has been executed, and then the next task is searched from the task queue (that is, the previously added timer task).
- Step 5: Execute the timer task, i.e
console.log('cccc')
With all that said, it’s easier to understand when you look at the picture
To sum up:
- Ongoing tasks (such as console.log AAA and BBB above) are never interrupted, and all asynchronous code is added to queues for execution
- A timer, no matter how short it is (no matter how short it is), is not executed immediately, but is placed at the end of the task queue
Off-topic: Why is the timer always wrong?
One thing you must have noticed is that JS timers are often inaccurate (in fact, this is true of all languages), and this problem is also related to the above task queue
- The time of the timer is not the time for the function to execute, but “add the task to the queue in the shortest x milliseconds”.
- That is, unless the queue is completely empty, the timer is up and it simply starts queuing
- So, is it possible for queues to be empty forever? Obviously not. Even without your task, the browser has a lot of work to do — rendering, rearranging, cleaning up memory, etc.
So the conclusion is that timers can never be exactly on time because there are other tasks lining up
Example: More timers, more tasks
If you are all right with the above things, give you another example to test your learning results
console.log('aaa');
setTimeout(() = > console.log(111), 0);
setTimeout(() = > console.log(222), 0);
console.log('bbb');
Copy the code
In short, setTimeout is not executed immediately, even if it is 0 milliseconds. Instead, the setTimeout is stacked at the end of the queue and the current task is not interrupted, so AAA and BBB come out first, and then take out a task from the end of the queue, namely 111, and then take out a task from the end of the queue, namely 222
Tasks are created equal, until someone rushes VIP
Now that we’ve talked about event loops and task queues, Blue is here to tell you something amazing.
Macro task? Micro tasks?
In fact, js task queue is not only one, but there are two, and one is SVIP annual fee platinum queue
- MacroTask (or Task for short) : A common Task that is normally executed
- MicroTask: SVIP annual platinum membership task, prior to macro task execution (but still non-steal)
In the case of microtasks, the JS event loop is executed in this order:
- Get the next task, if not enter wait
- When a task is completed, all queued microtasks are executed
- Then get the next task again
while(Get task ()){execute task (); ForEach (microtask =>{execute microtask (); }); }Copy the code
So, microtasks are actually a higher priority than normal tasks, because after one task ends, the event loop finds and executes all the microtasks, and then moves on to other tasks, but we have two problems:
- Which tasks are macro tasks and which are micro tasks?
- What’s so special about the macro quest, and why is it so privileged?
Which operations are macro tasks? Which are microtasks?
The original JS only had macro tasks, while micro tasks were added later
-
Macro tasks: Normal asynchronous tasks are macro tasks, the most common are setInterval (setImmediate, setTimeout), IO tasks
-
Microtasks: Microtasks are relatively new, queueMicrotask, Promise, and Async are microtasks (async, of course, is Promise)
With all that said, let’s take a look at an example to help you figure it out
console.log('aaa');
setTimeout(() = > console.log(111), 0); // Asynchronous tasks
queueMicrotask(() = > console.log(222)); // Asynchronous tasks
console.log('bbb');
Copy the code
Blue takes you through the implementation of this thing
- The first step, of course, is execution
aaa
There is no doubt about it - Step two, the timer doesn’t go off immediately, so
111
Queued up, but notice that timers are macro tasks - Step 3, queueMicrotask does not execute immediately either, so
222
I was in line, butPromise was in the VIP queue - Step 4, execute to
bbb
And that’s the end of the mission. Here’s the point - Step 5, before querying the task again (that while), all the microtasks will be completed first, so at this point,
222
Get priority. After all, it’s a VIP - Step 6, after completing all the microtasks (also known as step 5), it will look for the next task, at this point
111
That timer was executed
So, the entire execution is AAA, BBB,111,222, and now that we understand what microtasks are, microtasks are asynchronous tasks that get executed first, right
Why microtasks?
According to the official assumption, tasks are not equal. Some tasks have a great impact on user experience and should be executed first, while some tasks belong to background tasks (such as timers). There is no problem with late execution
Note: Async pit
As mentioned above, Promise is also a microtask, and async is a syntactic wrapper for Promise. Is async necessarily implemented as a microtask? Not all”
Here we go. Let’s go to the last example and scoop it up before we circle
console.log('aaa');
(async() = > {console.log(111); // In async
})().then(() = >{
console.log(222); // in async then
});
console.log('bbb');
Copy the code
Believe me, I don’t have to tell you that the holes in this program are 111 and 222. In other words, what is async?
First the effect, then the cause
- Step 1, again, is a no-brainer
aaa
, - Step 2, though
async
Async is an asynchronous operation, but async itself (i.e. 111 ()=>{}) is still executed synchronously, unless an await is received111
willDirect synchronous executionInstead of waiting in a queue - Step 3, here’s the thing,
then
It’s not synchronous, it’s asynchronous, and it’s a microtask, so222
It does not execute immediately, but to the end of the queue - Step 4, execute
bbb
There’s nothing to talk about, and the current mission is done - Step 5, finally queue the queue from the task queue
222
Take it out and finish the process
Is it easy to understand? ‘await’ is asynchronous, similar to ‘then’ (syntactically, ‘await’ is a promise ‘then’)
console.log('aaa');
(async() = > {console.log(111);
await console.log(222);
console.log(333);
})().then(() = >{
console.log(444);
});
console.log('ddd');
Copy the code
Let’s see how this thing works:
- Step 1,
aaa
Don’t say the - Step 2,
111
It’s synchronous, as I said - Step 3,
222
This is important. First of all, console.log itself is synchronized, soIt will be executed immediatelyWe can see it directly222
, butawait
Itself isthen
, soconsole.log(333)
Instead of executing directly, the queue is queued, and since async is not finished executing, its then (444) cannot fire - Step 4,
ddd
It should also go without saying that the current task is done here - Step 5, from the task queue
333
Then async is completed, so the then is queued for execution - Step 6
console.log(444)
Pull out the execution, see444
So, one conclusion is that ‘await’ is equivalent to ‘then’ (and in fact they are one and the same thing), which is to wait for subsequent tasks in a microtask queue without performing them immediately
Let’s solidify it again
Is that clear? Let me show you another example to reinforce it
console.log('aaa');
setTimeout(() = >console.log('t1'), 0);
(async() = > {console.log(111);
await console.log(222);
console.log(333);
setTimeout(() = >console.log('t2'), 0);
})().then(() = >{
console.log(444);
});
console.log('bbb');
Copy the code
First of all, there are a lot of holes in this example, but if you’re done with this example, you’re done with this class. Come on
- Step 1: No surprises
aaa
, - Step 2,
t1
Will be put into theTask queueWaiting for the - Step 3,
111
Will execute directly because async itself is not asynchronous. - Step 4,
222
I’m going to do it directly, but then I’m going toconsole.log(333);
andsetTimeout(()=>console.log('t2'), 0);
Just plug into theMicrotask queueIn waiting for the - Step 5,
bbb
No doubt, and when the current task is complete, the microtask queue takes precedence, which isconsole.log(333)
Where it started - Step 6: Execute
333
And then the timert2
Will joinTask queueWait (t1 and T2 in the task queue) and async completes, soconsole.log(444)
Enter theMicrotask queueWaiting for the - Step 7: Prioritize microtasks, i.e
444
At this point all the microtasks are completed - Step 8. Execute the rest of the normal task queue
t1
andt2
Will come out
conclusion
It’s time to go over what Blue said, so first of all
- Event loop: JS uses a single thread event loop to manage asynchronous tasks. The advantage is that it simplifies the programming model, but the disadvantage is that it cannot give full play to the CPU performance (but it has no impact on the front end).
- Task queue: JS runs in non-interrupt mode. The current task will not be interrupted. When a new asynchronous task is created, it will be put into the task queue
- Macro task, micro task: macro task is the common asynchronous task, is the first appeared, micro task is more about user experience, so it is preferentially executed
- Common macro tasks include timer and IO tasks
- Common microtasks: queueMicrotask, await, and then
5 – there are bugs? Would like to add?
Thank you for watching this tutorial, if you have any questions or want to talk to me, please leave a comment directly. If you find anything inappropriate in this article, please also point out, thanks in advance