Browser event loop

Why is there an event loop

Each render process in the browser has a main thread, and the main thread is very busy, dealing with DOM, styling, layout, JS tasks, and various input events. How do you get these different types of tasks to run smoothly? This requires a message queue and event loop.

Single thread

Because JS is single threaded, when we execute a piece of JS code, we put all the tasks in the main thread in order, and the tasks are executed in the thread in order. After execution, the thread will exit automatically, as shown in the following figure:

Event loops and message queues

However, not all tasks are uniformly scheduled before execution. When a thread is running, it needs to receive new tasks (such as user input)Event loop mechanism, can be simply understood as a thread loop waiting for events to occur.

In practical application, rendering main thread will frequently receive tasks from IO, such as responding to resource loading completion events, responding to click events, etc., how to ensure the execution sequence? The universal pattern is useThe message queue. Message queues are a first-in, first-out queue structure,If there are other threads that need to execute tasks in the main thread, they just need to add the tasks to the message queue.

Microtasks and macro tasks

Why microtasks, not just one type of task?

The “first in, first out” attribute of the queue determines the task in the message queue and must wait for the previous task to complete before it can be executed. In view of this, the following questions need to be solved: How to balance efficiency and real-time? For example, when Dom nodes change, the corresponding logic will be processed according to the change. A common design is to use JS to design a set of listening interfaces, and call these interfaces when the change occurs. The problem with this design is that if the Dom changes very frequently and the corresponding interface is called for each change, the execution time of the current task is lengthened, resulting in less efficient execution. If the asynchronous message events are placed at the end of the message queue, the real-time performance will be affected. In response to this situation, microtasks are born! Tasks in message queues are often referred to as macro tasks, and each macro task contains a queue of micro tasks. Microtasks are high-priority task types. The summary is: execute the macro task, and then execute the micro-task generated by the macro task. If a new micro-task is generated during the execution of the micro-task, continue to execute the micro-task. After the completion of the micro-task, return to the macro task for the next cycle.

Macro task

Most of the tasks in the page are performed on the main thread. These tasks include:

  • Render events (parsing DOM, calculating layout, drawing)
  • User interaction events (such as mouse clicks, page scrolling, zooming in and out, etc.)
  • JavaScript scripts execute events
  • Network request completed, file read/write completed event
  • SetTimeout and setInterval

Micro tasks

  • MutationObserver
  • Promise
  • JavaScript scripts execute events
  • Network request completed, file read/write completed event
  • SetTimeout and setInterval

Execution order

  1. Add macro tasks and microtasks to the main thread
    • Execution order: Thread => microtasks created on the main thread => macro tasks created on the main thread
  2. Create a microtask in a microtask
    • Execution order: Main thread => Microtask 1 created on the main thread => Microtask 2 created on microtask 1 => macro task created on the main thread
  3. Create a microtask in a macro task
    • Execution order: Main thread => macro task queue 1 on the main thread => micro tasks created in macro task queue 1
  4. The macro task created in the microtask queue
    • Execution order: Main thread => Micro tasks created on the main thread => macro tasks created on the main thread => macro tasks created on the micro task
  5. Async /await: two cases
    • If await is followed directly by a variable, such as await 1; In this case, it is equivalent to registering the code behind ‘await’ as a microtask, which can be simply interpreted as promise.then(code below ‘await’). Then jump out of async1 and execute other code.
    • If await is followed by an asynchronous function call. In this case, after executing awit, the code following await is not registered in the microtask queue first. Instead, after executing await, the async1 function is directly jumped out and other code is executed. After the execution of other code, we need to return to async1 function to execute the rest of the code, and register the code behind await in the microtask queue. Note that there are previously registered microtasks in the microtask queue.
Console. log('script start') async function async1() {await async2() console.log('async1 end')} async function async2() { console.log('async2 end') } async1() setTimeout(function() { console.log('setTimeout') }, 0) new Promise(resolve => { console.log('Promise') resolve() }) .then(function() { console.log('promise1') }) Then (function() {console.log('promise2')}) console.log('script end') Script start, async2 end, Promise, script end, AsynC1 end, promise1, promise2, setTimeoutCopy the code
Console. log('script start') async function async1() {await async2() console.log('async1 end')} async function async2() { console.log('async2 end') return Promise.resolve().then(()=>{ console.log('async2 end1') }) } async1() setTimeout(function() { console.log('setTimeout') }, 0) new Promise(resolve => { console.log('Promise') resolve() }) .then(function() { console.log('promise1') }) Then (function() {console.log('promise2')}) console.log('script end') Script start, Async2 end, Promise, Script end, AsynC2 end1, promise1, promise2, AsynC1 end, setTimeoutCopy the code

Event loop in Node

Node.js is a single-threaded, asynchronous, non-blocking, and highly concurrent language.

Why is there an event loop

Let’s take a look at what’s wrong with today’s server-side languages. In server-side languages such as Java, PHP, or ASP.NET, creating a new thread for each client connection takes about 2MB of memory per thread, which means that a server with 8GB of memory can theoretically connect to up to 4000 users at the same time. If you need to support more users, you need to increase the number of servers. Node.js modifies the client-to-server connection method. Instead of creating a new thread for each client connection, node.js fires an event for each client connection that is processed inside Node.js. There are two mechanisms used in Node.js:

  • Non-blocking I/O (execute the return result in a callback function without blocking other processing)
  • Event loop (only one event callback can be executed at a time, but other events can be processed in the middle of execution), and then return to execute the original event callback

Parse the event loop mechanism

Let’s take a look at the official picture, where each box is called a phase of the event loop.

Summary of stage

  • Timers: This stage executes scheduling callbacks that have been set by setTimeout() and setInterval().
    • Specifies a threshold at which the provided callback can be executed, rather than the exact time at which it is expected to be executed. We can only say early implementation. When to execute is controlled by the poll phase.
  • Pending Callbacks: I/O callbacks that are deferred until the next iteration of the loop
  • Idle,prepare: used only in the system
  • Poll: polling. Retrieve new I/O events; Perform I/ O-related callbacks: network connection, data retrieval, file reading, and so on. There are two important functions: 1. Calculate the time when I/O should be blocked and polled; 2. Process events in the polling queue.
    • If the polling queue is not empty, the event loop loops through the callback queue and synchronously executes them until the queue is exhausted or a system-specific hard limit is reached.
    • If the polling queue is empty:
      • If the polling queue is empty, it checks to see if any timers have reached the threshold. If any timers are ready, the event loop returns to the timer phase to perform the timer callback
      • If the script is dispatched by setImmediate, the script enters the Check phase to execute
      • If not dispatched by setImmediate, wait for the callback to be added to the queue and then execute.

  • The Check: setImmediate callback function is executed here
  • Close Callbacks: A closed callback function

process.nextTick

We can see that process.nextTick is not shown in the figure above because process.nextTick is not technically part of the loop. Node completes all synchronization tasks and then executes the process.nextTick task queue. If you want asynchronous tasks to execute as quickly as possible during development, you can do this using process.nexttick.

Execution order

Compare setImmediate and setTimeout

  • SetImmediate () is designed to execute the script once the current polling phase is complete.
  • SetTimeout () runs the script after the minimum threshold in ms.

If both are called in the main module, you are constrained by the delicacy of the process, for example, the output of the following code is indeterminate. Because it is constrained by process performance.

setTimeout(() => { console.log('timeout'); }, 0);
setImmediate(() => { console.log('immediate'); });
Copy the code

But if called in an I/O loop, setImmediate is always called preferentially, as follows:

const fs = require('fs'); fs.readFile(__filename,()=>{ setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); }); }) // output: immediate, timeoutCopy the code

The code analysis

When Node starts executing the script, it initializes the event loop, but does the following before the event loop has started.

  1. Synchronous tasks make asynchronous requests schedule when timers take effect execute process.nexttick (), etc
  2. Enter the event loop

Use a set of interview questions to analyze execution time:

async function async1(){ console.log('async1 start') await async2() console.log('async1 end') } async function async2(){  console.log('async2') } console.log('script start') setTimeout(function(){ console.log('setTimeout0') },0) setTimeout(function(){ console.log('setTimeout3') },3) setImmediate(() => console.log('setImmediate')); process.nextTick(() => console.log('nextTick')); async1(); new Promise(function(resolve){ console.log('promise1') resolve(); Console. log('promise2')}).then(function(){console.log('promise3')}) console.log('script end') Script start, async1 start, Async2, promise1, promise2, script end, nextTick, asynC1 End, Promise3, setTimeout0, setImmediate, setTimeout3Copy the code

Analysis:

  1. First step: output script start, async1 start, async2, promise1, promise2, script end
  2. After executing the script,
    • The setTimeout queue contains setTimeout0 and setTimeout3
    • SetImmediate In the queue: setImmediate
    • The process.nextTick queue has: nextTick
    • Microtask queues include async1 end and promise3
  3. Enter the event loop
    • Outputs the logic in the process.nextTick queue, which is nextTick
    • Promise: async1 end, promise3
    • Poll phase
      • It’s time for setTimeout0 to execute the setTimeout0 callback
      • setImmediate
      • setTimeout3

Microtasks, macro tasks, synchronous, asynchronous, Promise, Async, await, geek time browser how to work and practice