Event Loop

The basic concept

Event Loop is a very important concept, which refers to a kind of operation mechanism of computer system. The JavaScript language uses this mechanism to solve some of the problems associated with single-threaded running.

Javascript is a single-threaded language where everything is done on a single thread. When faced with a large number of tasks or a time-consuming task, the page will appear “suspended” because the JavaScript will not stop and will not respond to the user’s actions.

Single-threaded JavaScript

An overview of

One of the hallmarks of the JavaScript language is single-threaded, which means you can only do one thing at a time. So why can’t JavaScript have multiple threads? It’s more efficient.

The single thread of JavaScript, relative to its purpose. As a browser scripting language, JavaScript’s primary purpose is to interact with users and manipulate the DOM. This means that it has to be single-threaded, which can cause complex synchronization problems. For example, if there are two threads of JavaScript at the same time, one thread adds content to a DOM node, and the other thread removes that node, which thread should the browser use?

So, to avoid complexity, JavaScript has been single-threaded since its inception, and this has been a core feature of the language and will not change. In order to make use of the computing power of multi-core CPU, HTML5 proposes the Web Worker standard, which allows JavaScript scripts to create multiple threads, but the child threads are completely controlled by the main thread and cannot operate DOM. So, this new standard doesn’t change the single-threaded nature of JavaScript.

Why is JavaScript single threaded, and can’t it be multithreaded?

It has to do with history. JavaScript has been single-threaded since its birth. The reason is probably not to make the browser too complex, since multiple threads share resources and potentially modify each other’s results, which would be too complex for a web scripting language. JavaScript is a single-threaded language. (The Web Worker API can be multithreaded, but JavaScript itself is always single-threaded.)

If a task is time-consuming, such as involving a lot of I/O (input/output) operations, the thread might run like this.

The green part of the figure above is the running time of the program, and the red part is the waiting time. As you can see, the thread spends most of its running time waiting for the result of the I/O operation because the I/O operation is slow. This running mode is called synchronous I/O or blocking I/O.

Simply put, you have two threads in your program: one responsible for running the program itself, called the “main thread”; The other is responsible for the communication between the main thread and other threads (mainly I/O operations), and is called the “Event Loop thread “.

The green part of the main thread in the figure above, again, represents running time, and the orange part represents idle time. Each time an I/O is encountered, the main thread tells the Event Loop thread to notify the corresponding I/O program and then continues running, so there is no red wait time. When the I/O program finishes, the Event Loop thread returns the result to the main thread. The main thread calls the preset callback function to complete the task.

As you can see, with the extra orange free time, the main thread is able to run more tasks, which increases efficiency. This mode of operation is called “asynchronous I/O” or “non-blocking mode.”

This is exactly how the JavaScript language works. The single-threaded model, while limiting JavaScript, gives it advantages that other languages don’t. If deployed well, JavaScript programs don’t clog, which is why the Node.js platform can handle heavy traffic with very few resources.

Simply put, Event Loop is a mechanism developed by browsers to coordinate tasks such as Event processing, script execution, network requests, and rendering.

Microtasks & macro tasks

Macro task

Stands for discrete, independent units of work. The browser completes one macro task and executes the next

Before starting, the page is re-rendered. This includes creating document objects, parsing HTML, executing mainline JS code, and more

Events such as page loading, DOM events, input, network events, and timers.

Micro tasks

Microtasks are smaller tasks that are executed immediately after the execution of the current macro task. If there are microtasks, clear

The browser will clear the microtask and then re-render it. Examples of microtasks are Promise callbacks, DOM changes, and so on.

The difference between microtasks and macro tasks

  • Macro task: triggered after DOM rendering, specified by browser (Web APIs)
  • Microtasks: performed before DOM rendering, microtasks are an ES6 syntax requirement

Question to consider: DOM rendering, browser refresh?

learning

What does the following code output?

console.log('script start');

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

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

console.log('script end');
Copy the code

Jakearchibald.com/2015/tasks-…

The Event Loop process

Implementation process

In a nutshell, this is the following process:

  • The Event Loop iterates to fetch the oldest task in the Tasks queue and push it to the stack for execution. The Event Loop then executes and clears the tasks in the MicroTask queue.
  • After executing all the tasks in the MicroTask queue, it is possible to render updates (if there are DOM changes).
  • When the next macro task is executed, the DOM will have been re-rendered.

One more simplification: Before executing the next macro task, empty the microtask queue.

Synchronizing code is a macro task. When the microtask queue is empty, the browser rerenders it.

DOM rendering

Now that we know how the Event Loop works, let’s look at DOM rendering.

const main = document.getElementById('main');
const frg = document.createDocumentFragment();

for(let i = 0; i < 10; i++) {
  const li = document.createElement('li');
  li.innerHTML = i;
  frg.appendChild(li);
}
main.appendChild(frg);

new Promise((resolve) = > {
  resolve();
}).then(() = > {
  console.log('Microtask has been executed');
});

setTimeout(() = > {
  console.log('Macro task Execution');
});
Copy the code

thinking

  1. What happens to the page after line 9? (1) that I was in the main line 10. The innerHTML output out see not to know, please! This method does not reflect whether the DOM on the page has been rendered, because we are working with a DOM object, and the DOM object has changed. Of course, you can see the innerHTML of the output, but the actual DOM is not necessarily rendered. ② Or, I can put a breakpoint on line 10 and see what is displayed on the page.

[A] : It doesn’t work that way either. In debug mode, JS operations on the DOM are dynamic, that is, in this case, the DOM changes can be immediately seen on the page, but in the actual execution process, JS operations on the DOM do not immediately reflect the DOM changes.

  1. Once the microtask queue is empty, the DOM is re-rendered.

Can we find an intuitive way to test this statement?

B: Sure.

The first:

const main = document.getElementById('main');
const frg = document.createDocumentFragment();

for(let i = 0; i < 10; i++) {
  const li = document.createElement('li');
  li.innerHTML = i;
  frg.appendChild(li);
}
main.appendChild(frg);

new Promise((resolve) = > {
  resolve();
}).then(() = > {
  console.log('Microtask has been executed');
  alert('DOM not inserted yet');
});

setTimeout(() = > {
  console.log('Macro task Execution');
  alert('DOM is inserted');
});
Copy the code

First, you need to understand how alert works. If I don’t want to use alert, can I find a more intuitive way to verify?

The second:

const main = document.getElementById('main');
const frg = document.createDocumentFragment();

for(let i = 0; i < 10; i++) {
  const li = document.createElement('li');
  li.innerHTML = i;
  frg.appendChild(li);
}
main.appendChild(frg);

new Promise((resolve) = > {
  resolve();
}).then(() = > {
  console.log('Microtask has been executed');
  let sum = 0;
  for(let i=0; i<10000000000;i++){
    sum += i;
  }
});

setTimeout(() = > {
  console.log('Macro task Execution');
});
Copy the code

Output innerHTML, and look at the content of the page at the break point.

Knowledge check

async function asycn1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}

async function async2() {
  console.log('async2');
}

console.log('script start');

setTimeout(() = > {
  console.log('setTimeout ');
}, 0);

asycn1();

new Promise((resolve) = > {
  console.log('promise1');
  resolve();
}).then(() = > {
  console.log('promise2');
});

console.log('script end');
Copy the code