As the saying goes, know yourself and your enemy and you will win a hundred battles with no danger of defeat. If we want to understand the event loop, we need to know where the event loop is, and when we know where the event loop is, what it does, and what it does, it’s much easier to understand.

Where is the event loop?

Let’s look at the event loop first where is it? To understand this, we have to think about the interview question, “What does typing a URl into a rendering page do? “I think you already have the standard answer in your mind, but have you ever thought that after the browser gets the resources from the server, what the browser does is parse THE HTML, parse the CSS, build the DOM tree, build the CSS tree, synthesize the render tree, draw the layout, load the JS script? It turns out that this is not the case. One very important thing to do after building the page is that the user interaction generates events that are processed through the event loop.

Here is a brief picture I drew:

After the page in the diagram is rendered by the browser, it will enter the interaction phase. At this time, click events, keyboard events and so on generated by the user interaction are processed by the event loop.

Next, let’s focus on the queue in the figure above and zoom in, as shown below:

The queue in the figure above contains callback functions for various events and tasks produced by user interaction; Wait for a call from the call stack on a first-in, first-out basis.

It is worth noting that the browser can only execute one snippet of code at a time (single-threaded execution model). When an event is triggered, the browser needs to execute the relevant execution function to avoid the user waiting too long until the next event is triggered. So browsers need a way to keep track of events that have occurred but have not yet been processed. So here we introduce the event queue, which is the queue that we drew in the figure above.

It is also important to note that adding messages to the task queue takes place outside of the page build and event processing phases. And this process does not participate in the event processing thread. Event processing relies heavily on event queues, where all events are stored in the order in which they appear.

We have now identified the location of the event loop, which is located during the user’s interaction with the page, and that its role is to handle the events generated by the user’s interaction. So let’s dive into the event loop.

Deep event loop

In the full page life cycle, the event loop contains not only the event queue, but also other actions that the browser performs, called tasks, in addition to the event queue. The queue that holds these tasks is also called a task queue.

The task falls into two categories:

  • Macro task

There are many examples of macro tasks, including creating main document objects, parsing HTML, executing mainline JavaScript code, changing the URL of the current page, and various events such as page loading, input, network events, and timer tasks. From the browser’s point of view, macro tasks represent individual units of work, and after the tasks are run, the browser can perform other scheduling, such as re-rendering the page or performing garbage collection.

  • Micro tasks

Micro tasks, it is a smaller tasks, the task to update the application status, but must be executed before the browser task to continue to perform other tasks, the browser tasks include to render a page UI, micro tasks include the Promise of the callback function, DOM changes, such as micro tasks need as soon as possible, through asynchronous execution, At the same time, it does not generate entirely new microtasks, which allow us to perform specified behaviors before rerendering the UI, avoiding unnecessary UI redrawing that would cause the application state to be discontinuous.

The implementation of the event loop should contain at least one queue for macro tasks and at least one queue for microtasks. Most implementations typically have more macro and microtask queues for different types of tasks, which also allows the event loop to prioritize depending on the task type.

There are two kinds of tasks, microtasks and macro tasks, so there are macro task queues and microtask queues.

  • The event loop is based on two basic principles
  1. Tackle one task at a time

  2. A task that starts until it completes is not interrupted by other tasks.

Having looked at the basic concepts of macro tasks and microtasks, let’s now look at the diagram to learn how event loops work:

In the diagram:

Event loop will be the first to check the macro task queue, if the macro task waiting, then immediately start executing macro task, until to complete the task to run, the queue is empty (or task), event loop will move to handle task queue, if there is a task waiting in the queue, the event loop will, in turn, began to execute, complete a executed after the rest of the task, Until all microtasks in the queue have been executed.

Note the difference between processing macro tasks and queues of microtasks: in a single iteration, at most one macro task is processed (the rest waits in the queue), and all microtasks in the queue are processed.

When the microtask queue completes processing and clears, the event loop checks to see if an updated UI rendering is needed. The UI view is rerendered if needed. At this point, the current event loop is over, and you will return to the original first step, check the macro task queue again, and start a new event loop.

Details of the event loop

The event loop has a number of details that need to be clarified, as follows:

  1. Both the macro and microtask queues are independent of the event loop, which means that the addition of the task queue occurs outside of the event loop. The main reason is to prevent the browser from being slow to respond to user interactions by blocking the event loop. Therefore, the checking and adding of tasks is done independently of the event loop.

  2. Because JS is based on the single-threaded execution model, macro tasks and micro tasks are executed one by one. When a task starts to execute, it will not be interrupted by any task before it is completed, unless the browser decides to terminate the execution of the task, such as a task execution time is too long or the memory usage is too large.

  3. All microtasks are executed before the next render because they need to finish updating the state of the application before rendering the UI.

  4. Browsers typically attempt to render pages 60 times per second, reaching a speed of 60 frames per second, which means that browsers attempt to render a frame in 16ms, which is ideally the time within which a single task and all microtasks attached to that task should be completed.

Now let’s consider three things that could happen once the browser has rendered the page and moved on to the next iteration of the event loop.

  1. Before the end of another 16ms, the event loop executes the “render or not” decision. Because updating the UI is a complex operation, the browser may not choose to render the UI in the current loop without explicitly specifying that page rendering is required.

  2. About 16ms after the last render, the event loop executes to the “render or not” decision, in which case the browser will update the UI so that the user can have a smooth application experience.

  3. The next task (and all the microtasks associated with it) takes more than 16ms, in which case the browser will not be able to re-render the page at the visual frame rate and the UI will not be updated,

That concludes how event loops work.

Contains only examples of macro tasks

An inevitable result of JS single-threaded execution model is that only one task can be executed at a time, which means that all tasks must queue up in the queue waiting for execution time.

Now let’s try a simple example, a simple macro task example, look at the following example:

<script>
  setTimeout(() => {
    console.log('set-1')
  })
  console.log('a')
</script>
Copy the code

This is a very simple example, and I think you already know the answer.

A. set B. set C. set D. set

So what does the execution look like?

  1. When the page loads JS, the script fragment is added to the task queue and executed as a macro task.

  2. When the script fragment is executed, the macro task setTimeOut(()=>{},0) is first discovered, so the browser executes the macro task outside of the event loop and waits for the delay to arrive before adding it to the task queue.

  3. In the process does not block code execution, code will continue to execute down, print the console. The log (‘ a ‘), the browser, found that the current macro task has been completed will find micro task queue perform all tasks, but the existence of the current code sample no small task, so directly on the acer find next task queue of task execution.

  4. The callback function for setTimeOut enters the call stack for execution, printing console.log(‘set-1’).

Contains examples of macro tasks and microtasks

The next example is a bit more complicated than the last one, so let’s take a look at it together, before you read the answer.

SetTimeout (() => {console.log('set-1')}) setTimeout(() => {console.log('set-2')}, 100) setTimeout(() => { console.log('set-3') }, 10) const pro = new Promise((res, rej) => { console.log('1'); res('2') console.log('3') }) pro.then(res => { console.log("res", res) return '4' }).then(res => { console.log("res", res) }) setTimeout(() => { pro.then((res) => { console.log("res", res) console.log('5') }) console.log('7') }, 1000) setTimeout(async () => { console.log('9') const num = await pro.then((res) => { console.log("res", res) return '8' }); console.log("num", num) }, 0) console.log('6') </script>Copy the code

Try to draw and write with a pen and paper, code is a little too much, easy to remember wrong.

1, 3, 6, res 2, res 4, set 1, 9, res 2, num 8, set 3, set 2, 7, res 2, 5

Let’s see how it works:

  1. A script fragment starts execution by adding a macro task to the task queue.

  2. The code in this script snippet works its way up to the next execution, first encountering three setTimeouts, with browser controls waiting outside the event loop for the delay time to arrive, and then adding them to the macro task queue.

  3. Next we execute new Promise(), and since the Promise is an instant-execution function, we immediately execute console.log(‘1’) res(‘2’) console.log(‘3’), printing 1, 3.

  4. The code executes to pro.then().then() because these things are microtasks, so all of them are added to the microtask queue. The macro task will be executed after the macro task is completed.

  5. The next two setTimeOut() are still controlled by the browser, waiting outside the event loop for the delay to arrive, and then adding the macro task to the queue. These two macro tasks will wait to be added to the macro task queue along with the above three macro tasks. They will be added to the macro task queue in order according to the order of execution and wait time.

  6. Execute console.log(‘6’) and print 6. This macro task is finished. Next, view the microtask queue and execute all microtasks.

  7. Because the current microtask queue has pro.THEN ().THEN (), which executes all microtasks, res 2, RES 4 will be printed. The browser then renders the UI depending on the situation. Then look at the macro task queue and execute the next macro task.

  8. Start setTimeout(() => {console.log(‘set-1’)}); This macro task, the callback function is called to execute, prints set-1. Why does it get to the macro task queue first? Two reasons, one is because it doesn’t have a second argument, the browser will default to execute it after at least 16ms, and the other is because the code runs from top to bottom and it executes first. Then the system checks whether microtasks exist. If no microtasks exist, the next macro task is executed.

  9. Start setTimeout(async() => {},0); This macro task, prints 9 and waits for await to be synchronized, prints Res 2 and then goes down to print Num 8. So why is this macro task second in the macro task queue? Two reasons: one is because its second argument is 0, so the browser will still execute it at least 16ms later, and the other is because the previous macro task executed before it, so it is the second. Then the system checks whether microtasks exist. If no microtasks exist, the next macro task is executed.

  10. Begin to execute setTimeout () = > {the console. The log (‘ set – 3 ‘)}, 10); This macro task; Print the set – 3. Consider for yourself why this is the third execution. Then the system checks whether microtasks exist. If no, the next macro task is executed.

  11. Start setTimeout(() => {console.log(‘set-2’)}, 100); This macro task, prints set-2. Then the system checks whether microtasks exist. If no microtasks exist, the next macro task is executed.

  12. SetTimeout (() => {}, 1000) => setTimeout(() => {}, 1000) => setTimeout(() => {}, 1000); Print res 2,5. Then the system checks whether microtasks exist. If no microtasks exist, the next macro task is executed.

  13. If there is no macro task, wait for user interaction.

conclusion

  1. The processing logic of the event loop is as follows: first, the macro task in the macro task queue is processed, then all the microtasks in the current microtask queue are processed, then UI rendering is performed according to the situation, and then the next macro task in the macro task queue is processed. If there is no macro task, it waits for user interaction.

  2. Since JS is a single-threaded model, only one task can be performed at a time, which cannot be interrupted.

Past wonderful

  • React vs. Vue status update principles

  • What happens when React encounters a tree shuttle?

  • Top 10 Mistakes to avoid when using React

  • Guide to Optimizing WebPack Packaging

  • What does Vue do when dragging dynamically generated components?

  • JS Coding skills, most people do not!!

  • TypeScript doesn’t it smell good? Come now!