Back to topics

Event Loop

Process

It is the basic unit for resource allocation and scheduling by the operating system, and the dynamic process of program execution.

Thread

It is the smallest unit that an operating system can schedule for computation. It is contained within a process and is the actual operating unit within the process.

Browser Process Model

In a browser, there are many processes. The main ones are:

  1. Browser process: responsible for page display, user interaction, sub-process management, etc. The browser process internally starts multiple threads to handle different tasks.

  2. Network process: responsible for loading network resources; internally starts multiple threads to handle different network tasks.

  3. Rendering process: after the rendering process starts, it opens a main rendering thread, responsible for executing HTML/CSS/JS code. By default, each tab opens a new rendering process to ensure different tabs do not affect each other.

The main rendering thread is the thread we are most concerned about.

Main rendering thread workflow:

  1. Parse HTML

  2. Parse CSS

  3. Calculate styles, layout

  4. Process layers

  5. Render 60 times per second

  6. Execute global JS code

  7. Execute event handler functions

  8. Execute timer callback functions

So how should the main thread schedule?

  1. The main rendering thread enters an infinite loop.

  2. Each loop checks whether there is a task in the message queue; if so, it takes out one task to execute, and after execution, enters the next loop.

  3. All other threads, including threads from other processes, can add tasks to the message queue at any time.

This scheduling process is called the Event Loop.

Example:

btn.onClick = ()=>{
  h1.innerText = 'new text'
  sleep() // synchronous blocking
}

What happens with this code? The interaction thread listens for button click events. When a click occurs, it wraps the callback function into a task object and puts it into the message queue. The main thread takes the task from the task queue and executes it. When it executes the sleep function, it waits for several seconds. After the sleep function finishes, the page is rendered.

Task Queues

Each task has a task type, and tasks of the same type must be in the same queue. Tasks of different types can be in different queues.

Browsers must have a microtask queue, where the priority of tasks in the microtask queue is higher than other queues. As browser complexity increases, W3C no longer uses the term 'macrotask queue'. Instead, there are several other queues.

Currently, Chrome's implementation includes at least the following queues:

  • Delay queue: used for callback tasks after timers expire, priority is medium.

  • Interaction queue: used for event handling tasks generated after user operations, priority is high.

  • Microtask queue: priority is [highest].

Summary

Single threading is the reason for asynchrony, and the event loop is the implementation of asynchrony.

Regarding requestAnimationFrame (rAF), it has its own independent queue (often called the Animation Frame Callbacks Queue), which is called at the start of a rendering update. It can be simply understood as executing after microtasks have completed.

Test Questions

What does the following code output?

console.log('A (synchronous)');

setTimeout(() => {
  console.log('B (macrotask1)');
  Promise.resolve().then(() => {
    console.log('C (microtask1 - from macrotask1)');
  });
}, 0);

Promise.resolve().then(() => {
  console.log('D (microtask1)');
  setTimeout(() => {
    console.log('E (macrotask2)');
    Promise.resolve().then(() => {
      console.log('F (microtask2 - from macrotask2)');
    });
  }, 0);
});

(async () => {
  console.log('G (async synchronous part)');
  await Promise.resolve();
  console.log('H (after async await - microtask)');
  setTimeout(() => {
    console.log('I (macrotask3)');
    Promise.resolve().then(() => {
      console.log('J (microtask3 - from macrotask3)');
    });
  }, 0);
})();

Promise.resolve().then(() => {
  console.log('K (microtask2)');
});