Event Loop

Purpose of the Event Loop

JavaScript is single-threaded, and a task waits for the previous task to finish before executing, which can be called synchronous I/O or blocking I/O.

The Event Loop provides a non-blocking I/O running mode.

Unlike multithreading, where you can imagine an Event Loop is a server serving several tables, multithreading is a server serving several tables.

The Event Loop is introduced

Each of the following calls triggers an Event loop:

  • Asynchronous API call
  • Timer arrangement
  • process.nextTick()

Here is a diagram of the event Loop’s internal execution:

┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ┌ ─ > │ timers │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ pending Callbacks │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ idle, Prepare │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ incoming: │ │ │ poll │ < ─ ─ ─ ─ ─ ┤ connections, │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ data, Etc. │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ │ check │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ └ ─ ─ ┤ close callbacks │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘Copy the code

Each event loop is called a tick.

Each stage has a callback queue, and when the Event loop reaches a certain stage, it will execute the callback in the queue on a first-in, first-out basis, and there is a mechanism limit on the number of executions.

Overview of each stage

  • timers: performsetTimeout()setInterval()Planned callbacks
  • Pending: Perform some callback about system operations (typically I/O requests)
  • Idle, prepare: used only by the Node
  • Poll: Searches for new I/O events
  • check: performsetImmediate()The callback
  • close: Execute all.on('close')Event callback

Node checks between each event loop to see if there is an asynchronous operation or timer, and shuts down if there isn’t.

The timer timers

A timer specifies the starting point for the callback to execute, and the callback will run as scheduled as possible, but mostly a little late. Callbacks are not called exactly at a given time.

The poll phase actually controls when the timer executes.

Here’s an example:

const fs = require('fs');

setTimeout(() = > {
    console.log('finish timeout')},100)

// Suppose 'fs.readfile ()' uses 95 ms to read the file
fs.readFile('./text'.() = > {
    // Assume that the callback takes 10 ms to execute
    console.log('finish read file')})Copy the code

In this example, a timeout timer starts at 100 ms, and then asynchronous file reads begin. When the Event loop enters the poll phase, the phase queue is actually empty (fs.readfile () has not yet finished executing), so it waits until the start point of the timer is reached. 95 ms past, fs.readfile () finishes reading the file and adds its callback to the poll queue and executes. When the callback is completed in 10 ms and the queue is empty again, the Event loop returns to the Timers stage to execute the timer callback. The callback to the timer will then start executing at 105 MS.

To prevent event loop “starvation” caused by the continuous running of the poll phase, libuv (the implementation of Node.js Event Loop) sets a maximum value (depending on the system) to stop polling for more events.

Pending Callbacks to be executed

This phase performs some callbacks about system operations (typically I/O requests).

When an asynchronous operation (such as fs.readfile) is performed, Node sends an I/O request to the system. When the I/O operation ends or an error is encountered, the callback for the asynchronous operation is placed in the pending queue and executed in the next pending Callbacks.

Polling poll

The poll phase has two main functions:

  1. Calculate how long it should block and poll I/O, and then
  2. Run the event in the poll queue

When the Event loop enters the poll phase and there is no scheduled timer, one of the following two events will occur:

  • If the poll queue is not empty, the Event Loop synchronously iterates through the callbacks in the queue, within the maximum number limit (system dependent)
  • ifpollIf the queue is empty, one of two things will happen:
    • If the script issetImmediate()If it is planned, it will end immediatelypollPhase, and then entercheckStage to executesetImmediate()The callback
    • If the script is notsetImmediate()The event loop will wait for the callback to be queued and then execute it immediately

Check the check

This phase allows the developer to perform some callbacks immediately (after the poll phase), and if the poll phase is idle or setImmediate() piles up, the Check phase will continue instead of waiting (as described above).

SetImmediate () is actually a special timer that runs in a separate phase.

In summary (highlighted in the Node documentation), when the code executes, the Event loop ends up in the poll phase waiting for incoming connections, requests, and so on. However, if a callback is scheduled by the setImmediate() scheme, the poll phase immediately terminates and then the Check phase proceeds.

Close Callbacks

The close event is raised during this phase if the socket or handle is suddenly closed. This can also be triggered by process.nexttick (). `

setImmediate() vs setTimeout()

The two functions are similar, but behave differently in different scenarios:

  • setImmediate()Is designed to be used inpollPost-phase execution
  • setTimeout()Used to execute scripts after a period of time

In general, the timing capability is limited by system performance, and in non-I /O cycles (such as the main module), the order in which the two timers are executed is not determined:

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

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

But in an I/O cycle, setImmediate() always executes first:

const fs = require('fs')

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

process.nextTick()

Process.nexttick () is not technically part of the Event loop. Instead, nextTickQueue will run after the current operation completes, regardless of the current event loop phase. The operation here is defined as a transition to a C/C++ handle that then handles what JavaScript is supposed to do.

When process.nexttick () is called at any stage, its callback is executed before the Event loop continues. However, misuse of this mechanism can starve I/O operations and prevent event loops from entering the poll phase.

process.nextTick() vs setImmediate()

Remember that process.nexttick () executes immediately in the current phase and is a little more “immediate” than setImmediate().

Node recommends using setImmediate() in all situations because it’s easier to reason.

Why useprocess.nextTick()

  1. Allows the developer to handle errors, clean up unwanted resources, or try to initiate the request again before the Event loop continues
  2. It may be necessary to execute callbacks after the Call stack has expanded and before the Event loop continues

reference

The Node.js Event Loop, Timers, and process.nextTick() – Node.js

What is an Event Loop? – nguyen half-stretching

A complete guide to the Node.js event loop – Piero Borrelli

What you should know to really understand the Node.js Event Loop – Daniel Khan

How the Event Loop Works in Node.js – heynode