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 firstconsole.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: Executeconsole.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.econsole.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 executionaaaThere is no doubt about it
  • Step two, the timer doesn’t go off immediately, so111Queued up, but notice that timers are macro tasks
  • Step 3, queueMicrotask does not execute immediately either, so222I was in line, butPromise was in the VIP queue
  • Step 4, execute tobbbAnd 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,222Get 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 point111That 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-braineraaa,
  • Step 2, thoughasyncAsync is an asynchronous operation, but async itself (i.e. 111 ()=>{}) is still executed synchronously, unless an await is received111willDirect synchronous executionInstead of waiting in a queue
  • Step 3, here’s the thing,thenIt’s not synchronous, it’s asynchronous, and it’s a microtask, so222It does not execute immediately, but to the end of the queue
  • Step 4, executebbbThere’s nothing to talk about, and the current mission is done
  • Step 5, finally queue the queue from the task queue222Take 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,aaaDon’t say the
  • Step 2,111It’s synchronous, as I said
  • Step 3,222This is important. First of all, console.log itself is synchronized, soIt will be executed immediatelyWe can see it directly222, butawaitItself 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,dddIt should also go without saying that the current task is done here
  • Step 5, from the task queue333Then async is completed, so the then is queued for execution
  • Step 6console.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 surprisesaaa,
  • Step 2,t1Will be put into theTask queueWaiting for the
  • Step 3,111Will execute directly because async itself is not asynchronous.
  • Step 4,222I’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,bbbNo doubt, and when the current task is complete, the microtask queue takes precedence, which isconsole.log(333)Where it started
  • Step 6: Execute333And then the timert2Will 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.e444At this point all the microtasks are completed
  • Step 8. Execute the rest of the normal task queuet1andt2Will 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