This is the second day of my participation in Gwen Challenge

Event loop is the core of asynchronous JavaScript programming. Through the Event loop mechanism, single-threaded JavaScript has the ability to process tasks asynchronously

Asynchronous task queue

Asynchronous task queues fall into two categories

  • Macro task queue
  • Microtask queue

Both are used to hold asynchronous tasks

Why are asynchronous queues divided into macro and micro tasks?

In fact, after learning the Event loop for a long time, I suddenly realized and asked myself the original question: there is only one asynchronous queue, which can meet the requirements of asynchronous operations. Why are there two queues?

Answer: to cut in line!

The execution logic of macro and micro tasks is essentially to meet the queue-jumping requirements of asynchronous operations, so that a postinserted asynchronous operation can be executed as early as possible

Macro task (macrotasks)

API Web Node
DOM API
I/O
setTimeout
setInterval
setImmediate
requestAnimationFrame

Some places list UI Rendering as a macro task, but in the HTML specification, it’s actually a parallel step to microtasks

  • UI Rendering represents not a single task, but a queue of tasks.
  • It has some features that are different from normal macro and micro tasks
    • Firing occurs between the current microtask queue and the next macro task
    • A new requestAnimationFrame triggered during UI Rendering execution does not advance the currently executing UI Rendering queue. Instead, it enters the next UI Rendering queue

Micro tasks (microtasks)

API Web Node
process.nextTick
MutationObserver
Promise.then catch finally

NextTick is similar to the UI Rendering on the Web side

  • Process. nextTick also has its own task queue, the nextTick Queue
  • It has some characteristics that are different from normal microtasks
    • The firing time is between the current macro task and the current microtask queue

Enforcement mechanism

Execution mechanisms in the Web

The Event loop in the browser environment is clearly defined by the HTML5 specification and implemented by each major browser manufacturer. Here, it mainly involves the following browser threads:

  • JS engine threads: Mainly handle main stack tasks (synchronization tasks)
  • Asynchronous HTTP request thread: Mainly handles network requests, pushing the completed network request callback function into the event-triggering thread
  • Timer thread: Pushes the event trigger thread with timer callbacks completed and ready to execute
  • Event-triggered thread: A thread that stores macro and micro tasks

The basic flow

The execution mechanism of asynchronous queues, in a nutshell

  1. When the main stack is empty, the asynchronous task queue is read
  2. The tasks in the microtask queue are read and executed successively until the queue is empty
  3. The first task execution is then read from the macro task
  4. Repeat from step 2 until the macro task queue is empty

Synchronous task -> All microtasks -> UI Rendering -> Macro task -> All microtasks -> UI Rendering -> Next macro task ->...

If in the process of execution

  • Triggering a new macro task pushes it into the macro task queue to be read
  • If a new microtask is triggered, it will be pushed to the current microtask queue and executed in the current microtask queue

Synchronous tasks -> All microtasks -> Macro tasks (trigger new macro tasks and microtasks) -> All microtasks (including the newly triggered microtasks) -> UI Rendering -> Next macro task (the newly triggered macro task is thrust into the macro task list to be executed) -> .

  • Triggering a new UI Rendering pushes it to the next UI Rendering

Synchronous task -> All microtasks -> ALL microtasks -> UI Rendering (spawns new RAF) -> Macro task -> All microtasks -> UI Rendering (spawns new RAF) -> Next macro task ->...

The browser event callback triggered by the action

// html
<div class="parent" onclick="handleClick()">
    <div class="child" onclick="handleClick()"/>
</div>

// js
function handleClick() {
    Promise.resolve().then(() = > console.log('promise then'))
    setTimeout(() = > console.log('setTimeout msg'), 0)}Copy the code

In the code above, if the user clicks on the Child element similar to the macro task trigger, Child click -> Child Promise then -> parent click -> Parent Promise then -> Child setTimeout msg -> parent setTimeout msg

Browser event callback triggered by code

Same code as above, if JS code is used to trigger the event

document.querySelector('.child').click()
Copy the code

So just like dispatchEvent, Child click -> parent click -> Child Promise then -> Parent Promise then -> Child setTimeout msg -> parent setTimeout msg

The execution mechanism in Node

Just as the Event loop on the Web side relies on the browser thread, the Event loop on the Node side also relies on a new student: Libuv

  • Libuv is Node’s new cross-platform abstraction layer that provides I/O event loops and asynchronous callbacks at its core
  • Libuv uses asynchronous, event-driven programming
  • Libuv’s API includes time, non-blocking networks, asynchronous file operations, child processes, and more.
  • The Event Loop is implemented in Libuv.

Six stages

The Event loop of Node is divided into six stages, which will be run repeatedly in sequence. When entering a stage, functions will be taken out from the corresponding callback queue to execute. When the queue is empty or the number of callback functions executed reaches the threshold set by the system, the next stage will be entered

Each detail is as follows:

  1. Timers: The callback that expires in setTimeout and setInterval is executed and the poll scheduling enters this stage
  2. Pending: Certain system operation-level callbacks are executed at this stage
  3. Idle, prepare: Used only internally.
  4. Poll: Perform I/O callbacks and, where appropriate, back block at this stage.
  5. Check: Executes the setImmediate callback function
  6. Close: Callback to execute the close event
timers
  • The Timers phase executes setTimeout and setInterval callbacks and poll scheduling enters this phase
  • Timers If new setTimeout and setInterval are triggered in the Timers phase, the timers phase is pushed to the next one and is not executed in the current timers phase
poll

This stage deals with two main things

  1. Return to the Timers phase to perform the callback
  2. Perform I/O callback

Execution logic:

Basic flow for Node 10.x and before

On Node 10.x and before. Each stage of the Event loop executes the macro task queue first, and then all macro tasks -> all nextTick tasks -> all microtasks of the microtask queue

Basic flow for Node 11.x and beyond

After node.js is upgraded to 11.x, the operation principle of Event Loop has changed. When a macro task is completed, the microtask queue is executed, which is consistent with the browser macro task -> All nextTick Tasks -> All Microtasks -> Next macro task -> All nextTick tasks -> All microtasks

conclusion

On the Web side, Event Loop relies on the implementation of various browser manufacturers. In addition to normal macro and micro tasks, it also has unique UI Rendering and MutationObserver to complete the loop of Event loop depending on the cooperation of various browser threads

However, on the Node side, Event loop depends on the implementation of Libuv, and there are differences before and after the Node 11 version. The Node side has 6 Event stages, and each stage can carry out the Event loop

Refer to the article

Understanding Event Loop once (How to Solve Interview Problems once) Interview Questions: Talk about Event Loops (Full Answers)