Knowledge points:

  • What are the advantages and disadvantages of single threading, and why do you need an event loop?
  • What about the call stack and the task queue?
  • What exactly is the event loop?
  • Why microtasks? What are microtasks and macrotasks?
  • What is the order of execution of micro and macro tasks?

Advantages and disadvantages of single threading

Javascript is a single-threaded, non-blocking language. The big advantage of single threading is that you don’t have to worry about state synchronization as much as multithreaded programming does. If JS is a multithreaded language, and there are two or more threads working on a DOM at the same time, how should the DOM be represented? Therefore, in order to avoid this kind of problem, JS chooses to use a single thread to execute the code, so as to ensure the consistency of the program.

However, using single thread brings new trouble: for example, because JS and UI share the same thread, if JS code is executed for a long time, it is bound to cause the UI to be unable to render, which will seriously affect the user experience.

So, is there a way to solve this problem? The answer is to use asynchronous callbacks to solve thread blocking. This involves a very core knowledge point in JS: Event Loop mechanism.

Call stack and task queue

Before we get into event loops, let’s familiarize ourselves with the concepts of call stacks and task queues. The call stack is a data structure used to manage the call relationships of functions. If we execute a function, we push that function onto the stack; If the function completes, it is popped from the stack. Such as:

function multiply(x, y) {
    return x * y;
}
function printSquare(x) {
    var s = multiply(x, x);
    console.log(s);
}
printSquare(5);
Copy the code

The call stack is empty at the beginning, followed by the following steps:

When we call a function in an infinite loop, we keep adding the called function to the call stack. When a certain value is reached, it exceeds the stack memory, which is called stack overflow.

So that’s synchronous code execution, but what happens when you have an asynchronous task? Let’s start with a picture:

Call Stack refers to the Call Stack.

Web APIs represent asynchronous events in JS.

The Callback Queue is a task Queue composed of Callback functions to store tasks to be executed.

The task queue conforms to the characteristics of the queue “first in, first out”, that is, to add tasks to the end of the queue; To fetch a task, fetch it from the head of the queue.

But what exactly is the relationship between call stacks, Web APIs, and message queues? This is where we need to understand the event loop.

Event loop mechanism

On MDN, the event loop description is correct: it is called an event loop because it is often implemented as follows:

While (queue.waitFormessage ()) {queue.processNextMessage(); }Copy the code

Combined with the task queue mentioned above, it can be understood that the event cycle is to continuously remove tasks from the task queue, then execute tasks and wait for new tasks. Let’s take a look at what happens in this code:

console.log(1)
setTimeout(() => { console.log(3) }, 5000)
console.log(2)
Copy the code

First of all, this whole piece of code can be viewed as A task, and then it gets pulled out and executed, which is called task A. When a setTimeout is encountered (that is, when the Web APIs are invoked), it is executed asynchronously. Will the current task A wait for setTimeout to execute and continue to execute the next section of code? When the code execution is complete, task A will be considered finished. At this point, the task is fetched from the task queue, and no task is found, so the wait starts. When setTimeout completes, the callback function is added to the task queue. So the next time I fetch a task from the task queue and find a callback function, I fetch the callback function, call it task B, and execute task B. And so on…

Therefore, the event loop is a cycle of task fetching, task executing and task waiting.

Macro and micro tasks

Why introduce the concept of macro and micro tasks? In our task queue, it is actually impossible to distinguish priorities, and all tasks are arranged according to the time order they are added, which obviously does not meet some situations with high real-time requirements. Therefore, in order to solve this problem, the concept of macro task and micro task is introduced to divide tasks into priority. So what is a macro task and what is a micro task?

There is no specific definition here, but it can be roughly divided according to the types of asynchronous functions:

Macro task: containing the whole js code, event callback, XHR callback, the timer (setTimeout/setInterval/setImmediate), IO operations, UI render.

Microtasks: Tasks that update application state, including promise callbacks, MutationObserver, process.nextTick, object. observe.

Next, let’s focus on the execution sequence of microtasks and macrotasks. Here is a picture:

The queue composed of macroTasks is the task queue we know above. As you can see from this figure, there is a queue of microtasks between each macro task. After the current macro task is executed, each task in the microtask queue is executed before the next macro task is fetched. Here are some specific examples:

console.log('start')

setTimeout(function() {
  console.log('setTimeout')
}, 0)

Promise.resolve().then(function() {
  console.log('promise1')
}).then(function() {
  console.log('promise2')
})

console.log('end')
Copy the code
  1. First, let’s think of this whole code as A macro task, called task A, which prints start;

  2. When setTimeout is executed, task A skips setTimeout and continues to execute. SetTimeout will add the callback function to the task queue after the time expires. Here, the callback function is denoted as task B.

  3. When the Promise is executed, task A skips the Promise and continues. Then1 is added to the microtask queue after task A and before task B, denoted as microtask C.

  4. Task A continues, printing end.

  5. When the code is finished, it will fetch the task from the microtask queue, so it gets task C and executes, printing promise1. Then2 was encountered during execution, so then2 was added to the end of the current microtask queue, denoted as task D. Task C is completed.

  6. Continue fetching tasks from the microtask queue, select task D and execute it, print **promise2, and ** task D is completed.

  7. Continue fetching tasks from the microtask queue and find no tasks. Then go to the task queue to fetch the next macro task, get task B and execute, print **setTimeout, ** task B is completed.

  8. Repeat the above steps to fetch the microtask and macro task.

Therefore, the final print is:

start
end
promise1
promise2
setTimeout
Copy the code

GIF image to get a more intuitive view of the process:

At this point, the event loop mechanism in the browser is pretty much there. One thing to note here is that you are fetching one macro task at a time, and between the macro tasks is a queue of microtasks that may have multiple microtasks. In addition, if a microtask is executed in a microtask, a new microtask will also be added to the back of the current microtask queue, so the newly added microtask takes precedence over the next macro task.

reference

  • Node.js
  • Explain the Event Loop mechanism in JavaScript
  • Working principle and practice of browser
  • The illustration Google V8
  • how does javascript actually work
  • MDN Event Loop
  • An in-depth understanding of the JS event loop (Browser)