What is the event loop

The concept of an event loop is very simple. It is an infinite loop between the JavaScript engine waiting for a task, executing a task, and going to sleep for more tasks.

It is responsible for executing code, collecting and processing events, and executing subtasks in queues.

The logic is very simple:

  1. When there is a task: Set the task

  2. The JavaScript engine starts with the task that comes in first

  3. Rest, wait for tasks to appear, and then continue from step 1

There are, however, more detailed questions that need to be clarified.

1. What are the tasks?

Generally speaking, tasks are divided into the following parts:

  1. call<script src="..." >The code in
  2. User events
  3. Timer task
  4. Promise the task
  5. HTTP Request Task
  6. .

According to the way the above tasks are called, we can also be divided into synchronous tasks and asynchronous tasks.

We know that Javascript is a single-threaded language, and the main thread can only do one thing anyway. But the environment will provide Javascript multithreading capabilities.

This asynchronous scheduling capability is granted by the browser or node environment and managed by the V8 engine. It has the advantage of enabling JavaScript code to execute asynchronously without blocking I/O. (For the moment, we can think of an asynchronous call as running some code in the background and then waiting for the right moment to output it.)

We need to understand how synchronous and asynchronous tasks in the Event loop are managed and scheduled

2. Where are the tasks?

Multiple tasks form a task queue, known as a “macro task queue”

Task queues follow a first-in, first-out principle, as in the figure above, where the JavaScript engine runs script code first, then user events, then timer tasks.

Here are two details:

  • When the engine executes a macro task, render does not execute, so the GUI thread (the thread responsible for rendering the page) is mutually exclusive with the Javascript engine

  • The next task will only be executed when the previous one is finished, i.e. the JS engine can only do one thing at a time.

Since missions are first in, first out, we need to figure out which missions are advanced and which are backward

How setTimeout works

To understand how synchronous and asynchronous tasks are managed and scheduled, what code do we need to know is asynchronous

  • SetTimeout and setInterval
  • Click on the event
  • promise.then
  • xmlHttpRequest.send

All of the above code is asynchronous. Let’s start with setTimeout. How does it work?

let start = Date.now();
setTimeout(() = > {
  console.log("SetTimeout for asynchronous task 1".Date.now() - start, "ms");
}, 10000);// This is a 10s timer

for (let i = 0; i < 10000000000; i++) {
  // This is a time-consuming synchronization task around 12-13 seconds
}
// After the preceding tasks are completed, the following synchronization tasks will be performed
let duration = Date.now() - start;
console.log("The synchronization task cost.", duration, "ms");
Copy the code

I set a 10s timer on it and then performed the synchronization task and saw this effect on my computer

The synchronization task took 13009mssetTimeoutAsynchronous task of1 
13110ms 
Copy the code

As you can see, the asynchronous task was set to 10 seconds, but was actually printed after 13 seconds, which shows

  • Asynchronous tasks take precedence over synchronous tasks. As long as synchronous tasks are being performed, asynchronous tasks cannot run
  • SetTimeout starts the timer after it’s called, which you can think of as counting down in the background, in parallel with the for loop

The first conclusion is easy to understand. JavaScript is single-threaded, and its rule is to execute synchronous code first, then asynchronous code.

So in combination with the second conclusion, where does setTimeout count down?

The answer is a browser-provided timed trigger thread, which places code on a task queue and then invokes the timer task after the synchronization task has finished.

When does the task in the timer enter the task queue

Since it is called a task queue, it meets the first-in, first-out condition. If there are two timers, how does it determine who is first? This question extends to when the task in setTimeout enters the task queue.

Let’s go ahead and do some experiments to prove that, and then the code changes to zero

let start = Date.now();
setTimeout(() = > {
  console.log("SetTimeout for asynchronous task 1".Date.now() - start, "ms");
}, 10);

setTimeout(() = > {
  console.log("SetTimeout for asynchronous task 2".Date.now() - start, "ms");
}, 0);

setTimeout(() = > {
  console.log("SetTimeout for asynchronous task 3".Date.now() - start, "ms");
}, 0);

for (let i = 0; i < 1000000000; i++) {
  // This is a synchronization task that doesn't take much time, about 1 second
}
// After the preceding tasks are completed, the following synchronization tasks will be performed
let duration = Date.now() - start;
console.log("The synchronization task cost.", duration, "ms");
Copy the code

The result is zero

The synchronization task cost959 ms

setTimeoutAsynchronous task of2 
961 ms

setTimeoutAsynchronous task of3 
961 ms
 
setTimeoutAsynchronous task of1 
1139 ms
Copy the code

Obviously, the time when setTimeout pushes to the task queue is related to the time set by the timer. We can draw the following conclusions

  • SetTimeout pushes tasks to the task queue based on the completion of the timer, not the code order
  • If the time set between two timers is infinitely close (or the same), it depends on code order

Event code and timer who is fast

Next we need to add the event code, we need to know the target is the event code or timer which is faster, modify the code to the following

const button = document.querySelector("button");
let start = Date.now();
button.addEventListener("click".() = > {
  console.log("Button is clicked.".Date.now() - start, "ms"); // This function is an asynchronous task
});

setTimeout(() = > {
  console.log("SetTimeout for asynchronous task 1".Date.now() - start, "ms");
}, 0);

for (let i = 0; i < 10000000000; i++) {
  // This is a time-consuming synchronization task of about 12 seconds
}
// After the preceding tasks are completed, the following synchronization tasks will be performed
let duration = Date.now() - start;
console.log("The synchronization task cost.", duration, "ms");
Copy the code

When I ran the code above, I clicked button while waiting 12 seconds for the loop to end

At this point, the main thread is still executing the synchronization code of the for loop, so the button button feels blocked. You will notice that the normal press feeling of clicking button is gone. This is because the JS thread is performing its task and will not trigger the GUI thread to render the page, so the page will have the illusion of being stuck. But at this point the click event has actually been triggered.

Let’s look at the results

The synchronization task cost13013The MS button is clicked13017 ms
setTimeoutAsynchronous task of1 
13019 ms
Copy the code

Why is the button event executed first and not setTimeout?

If you’re familiar with first-in, first-out queue structures, you might be wondering if, from the code, the click event will be pushed to the task queue later than the 0 timer anyway, so the timer should be executed first.

Because events process user interaction logic, they must have a higher priority than timers and are pushed to the task queue first.

The event is processed by the event processing thread and pushed to the task queue after the event occurs. Until the JS thread completes the synchronization code, and then processes the event task of the task queue, and finally processes the timer.

Promise, event, timer who is fast

It’s the same code, and we add the promise logic

.// The code above is consistent, so it is omitted here

Promise.resolve().then(() = > {
  console.log("Asynchronous tasks for Promise".Date.now() - start, "ms");
});

for (let i = 0; i < 10000000000; i++) {
  // This is a time-consuming synchronization task of about 12 seconds
}
// After the preceding tasks are completed, the following synchronization tasks will be performed
let duration = Date.now() - start;
console.log("The synchronization task cost.", duration, "ms");
Copy the code

Similarly, WHILE executing the code for loop, I click the Button and wait for 12s to see the result

The synchronization task cost12985 ms 
PromiseAsynchronous task of12986The MS button is clicked12988 ms 
setTimeoutAsynchronous task of1 
12990 ms
Copy the code

It can be found that primise. Then is faster, followed by click events, and finally timing triggers.

summary

  • In addition to JS threads, browsers also have event processing threads and timing trigger threads. It is the assistance of these threads that makes JS have the ability to process tasks asynchronously
  • Asynchronous task processing is to wait until the appropriate time (such as timer expiration, user click) to push the task to the queue, the JS engine in order to get the task in the queue to execute.
  • Different threads push tasks with different priorities. Events deal specifically with user interactions and are pushed to a task queue with a higher priority than timed triggers

Macro and micro tasks

Why was Promise the fastest?

This is because task queues are also divided into macro tasks and micro tasks. After each macro task, the engine immediately executes all tasks in the microtask queue before performing other macro tasks, or rendering, or anything else.

  • Often the first macro task isscript, it is the first to enter the task queue. At this time, the code in the script will be different from the synchronous code in the execution process. The asynchronous code is suspended and the synchronous code is executed first
  • After executing the synchronization code, all of thepromiseThe microtasks composed of, etc., are preferentially discharged into the task queue to form the microtask queue and process the microtasks one by one
  • After processing the microtask, the JavaScript engine hangs temporarily and the GUI thread comes to work, performing the page render once
  • Then process the next macro task, the user event with higher priority (if triggered) will be discharged into the task queue, at this time the user click event (asynchronous code) if there is microtask code, then after the synchronization code execution in the click event, again into the microtask queue execution, finally render
  • Finally processing timer task, logic with the above basic consistent
  • .

Common macro tasks are as follows

  1. setTimeout
  2. setInterval
  3. setImmdiate
  4. I/O

Common microtasks are as follows

  1. Promise.then
  2. process.nextTick
  3. Object.observe
  4. MutationObserver
  5. queueMicrotask

Validation test

You may be a little skeptical of my logic above, but here are some of the things you doubt

  • GUI hangs while JS engine is executing tasks?
  • When the macro task is finished and the micro task is completed, the page is rendr once. And then the next macro task, micro task, render cycle?

Verify the first one

First, the verification of the first problem, which is very easy to do, is as follows

const button = document.querySelector("button");
let start = Date.now();

for (let i = 0; i < 1000000; i++) {
  button.innerHTML = "I am a button" + i;
}
// After the preceding tasks are completed, the following synchronization tasks will be performed
let duration = Date.now() - start;
console.log("The synchronization task cost.", duration, "ms");
Copy the code

We can see that it takes three seconds for the page to trigger render, which means we are correct and render is invalid when we execute the synchronization code. (otherwise render while executing)

Verify the second

Next, we verify the second problem: here we add microtasks, macro tasks, to trigger click events at the right time, and the code looks like this

const button = document.querySelector("button");
let start = Date.now();

for (let i = 0; i < 1000000; i++) {
  // This is a time-consuming synchronization task of about 3 seconds
  button.innerHTML = "I am a button" + i;
}
Promise.resolve().then(() = > {
  console.log("Microtask triggered.".Date.now() - start + "ms");
  button.innerHTML = Date.now() - start + "ms";
});
button.addEventListener("click".() = > {
  console.log("Click event triggered".Date.now() - start, "ms");
});
// After the preceding tasks are completed, the following synchronization tasks will be performed
let duration = Date.now() - start;
console.log("The synchronization task cost.", duration, "ms");


Copy the code

While WAITING for the result for 3s, I clicked a button and saw the result:

According to the results, we can find that

  1. The page does render after the microtask
  2. After render, the engine executes the click event

The experiment mentioned above proves that our conclusion is correct

  • The JS engine first executes the synchronous code in script and suspends the asynchronous code
  • After the synchronous code is executed, other threads will push the task to the task queue. At this time, the micro task will enter first, and the macro task will enter later
  • After the microtask runs, it is rendered once, followed by the next macro task in the task queue
  • Macro task processing end – micro task -render
  • And so on…

Verify the conclusions

Can we make the for loop follow render as well?

The answer is in our conclusion, each macro task, micro task run end, will execute render. I’ll just start the macro task in the for loop and render the page.

To verify our results, let’s change the code again

const button = document.querySelector("button");

button.addEventListener("click".() = > {
  for (let i = 0; i < 100000; i++) {
    // This is a time-consuming synchronization task of about 3 seconds
    setTimeout(() = > {
      button.innerHTML = "I am a button" + i;
    }, 0); }});Copy the code

As you can see, we are starting 100,000 timer tasks, and each time the timer task is completed, we render the page before starting the next timer task.

The code above still waits for the end of the for loop before starting each timer task, which we need to know.

Finally, we’ll modify the code using the queueMicrotask API,

const button = document.querySelector("button");

button.addEventListener("click".() = > {
  for (let i = 0; i < 100000; i++) {
    // This is a time-consuming synchronization task of about 3 seconds
    queueMicrotask(() = > {
      button.innerHTML = "I am a button"+ i; }); }});Copy the code

This API is a microtask, and now we need to reiterate our conclusion:

  1. When the for loop ends, microtasks start
  2. There will be no render page until the end of the micromission
  3. QueueMicrotask belongs to the micro-Task queue
  4. So queueMicrotask will not render until it is all finished

Let’s verify this with the results

conclusion

  1. An event loop is an infinite loop where the JavaScript engine waits for tasks, executes tasks, and sleeps. It receives tasks through a macro task queue, completes them first and does only one thing at a time.
  2. The first task in the macro task queue is script, which is executed first
  3. When the macro task is finished, the microtask queue starts to execute, and then the page is rendered once before the next macro task
  4. In macro tasks, events enter the macro task queue before timers (higher priority)
  5. Microtasks are usually promise. then, queueMicrotask, etc
  6. JavaScript is single-threaded, but both browsers and Node environments equip it with multiple threads to aid in its asynchronous capabilities
  7. There are many other threads, such as GUI threads (managing page rendering, backflow redrawing), event processing threads, timing trigger threads, etc.
  8. Other threads cannot manipulate the DOM in the same way as the JavaScript main thread
  9. The concurrency model of the task management sequence that the JavaScript hosting environment is equipped with is based on event loops.
  10. This article is about event loops in browsers

The content of this article comes from Github, which is a blog project I carefully maintain, recording important knowledge points of the front end, involving in-depth Javascript, HTTP protocol, data structure and algorithm, browser principle, ES6 and other contents. If you like or are inspired by it, welcome star and encourage the author.