How to get started quickly with VUE3.0: Learn
Although JavaScript is single-threaded, the event loop uses the system kernel as much as possible allowing Node.js to perform non-blocking I/O operations. Although most modern kernels are multi-threaded, they can handle multi-threaded tasks in the background. When a task is completed, the kernel tells Node.js, and then the appropriate callback will be added to the loop for execution. This article will introduce this topic in further detail.
When Node.js starts execution, the event loop will first be initialized. , process the provided input script (or put it into the REPL, which is not covered in this document). This will perform an asynchronous API call, schedule a timer, or call process.nextTick(), and then start processing the event loop. The
following figure shows the event loop execution sequence. Simplified Overview
┌──────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ │ └─────────────┬────────────┘ ┌────────────────┐ │ ┌─────────────┴────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └─────────────┬─────────────┘ │ data, etc. │ │ ┌─────────────┴────────────┐ └────────────────┘ │ │ check │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ └────────────────────────────┘Each
box represents a stage of the event loop.
Each stage has a FIFO queue callback execution. However, each stage is executed in its own way. Generally speaking, when the event loop enters a stage, it will perform any operations in the current stage and start executing callbacks in the queue of the current stage until the queue is completely consumed or executed. the maximum data. When the queue is exhausted or reaches its maximum size, the event loop moves to the next phase.
In each process of the event loop, Node .js checks to see if it is waiting for asynchronous I/O and a timer, and if not shuts down
A timer specifies the critical point at which a callback will be executed, rather than the time one wants it to execute. The timer will Execute as soon as possible after the specified elapsed time, however, operating system scheduling or other callbacks can delay execution.
Technically speaking, the poll phase determines when the callback is executed.
For example, you set a timer to execute after 100 ms, but your script reads a file asynchronously and takes 95ms
const fs = require('fs') ; function someAsyncOperation(callback) { // Assume this takes 95ms to complete fs.readFile('/path/to/file', callback); } const timeoutScheduled = Date.now(); setTimeout(() => { const delay = Date.now() - timeoutScheduled; console.log(`${delay}ms have passed since I was scheduled`); }, 100); // do someAsyncOperation which takes 95 ms to complete someAsyncOperation(() => { const startCallback = Date.now(); // do something that will take 10ms... while (Date.now() - startCallback < 10) { // do nothing } });
When the event loop enters the poll phase, there is an empty queue (fs.readFile() has not yet completed), so it will wait for the remaining milliseconds until the fastest timer threshold is reached. After 95 ms, fs.readFile() has completed reading the file and will take 10 ms to add to the poll phase and complete execution. When the callback is completed, there are no callbacks in the queue to be executed, and the event loop returns to the timers phase to execute the timer callback. In this example, you will see that the timer is delayed for 105 ms before executing.
To prevent the poll phase from blocking the event loop, libuv (the C language library that implements the event loop and all asynchronous behavior on the platform) also has a poll phase. max stop polling more events
This phase executes callbacks for certain system operations (such as TCP error types). For example, some *nix systems want to wait for an error to be reported if a TCP socket receives ECONNREFUSED when trying to connect. This will be queued for execution during the pending callback phase.
The poll phase has two main functions
When the event loop enters the poll phase and there is no timer, the following two things happen.
Once the poll queue is empty, the event loop will detect whether the timer has expired. If so, the event loop will reach the timers stage and execute the timer callback
this stage. Allows people to execute callbacks immediately after the poll phase is completed. If the polling phase becomes idle and the script has been queued using setImmediate(), the event loop may continue to the check phase instead of waiting.
setImmediate() is actually a special timer that runs in a separate phase of the event loop. It uses a libuv API to schedule callbacks to be executed after the poll phase is completed.
Typically, as code executes, the event loop eventually reaches the poll phase, where it waits for incoming connections, requests, etc. However, if a callback is scheduled using setImmediate() and the poll phase becomes idle, it will end and continue with the check phase instead of waiting for the poll event.
If a socket or operation is closed suddenly (eg socket.destroy()), the close event will be sent to this stage, otherwise it will be sent via process.nextTick() setImmediate
setImmediate() and setTimeout( ) are similar, but behave differently depending on when they are called
The order in which each callback is executed depends on the order in which they are called In this environment, if the same module is called at the same time, the time will be limited by the performance of the process (this will also be affected by other applications running on this machine).
For example, if we do not run the following script in I/O , although it is affected by process performance, it is not possible to determine the execution order of these two timers:
// timeout_vs_immediate.js setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); });
$ node timeout_vs_immediate.js timeout immediate $ node timeout_vs_immediate.js immediate timeout
However, if you move into the I/O loop, the immediate callback will always be executed first
// timeout_vs_immediate.js const fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); }); });
$ node timeout_vs_immediate.js immediate timeout $ node timeout_vs_immediate.js immediateThe advantage of
timeout
setImmediate over setTimeout is that setImmediate will always be executed first before any timer in I/O, regardless of how many timers exist.
Although process.nextTick() is part of the asynchronous API, you may have noticed that it does not appear in the diagram. This is because process.nextTick() is not part of the event loop technology. Instead, the current operation is executed After completion, nextTickQueue will be executed regardless of the current stage of the event loop. Here, operations are defined as transformations from the underlying C/C++ handler and handle the JavaScript that needs to be executed. According to the diagram, you can call process.nextTick() at any stage. All callbacks passed to process.nextTick() will be executed before the event loop continues execution. This can lead to some bad situations because it allows you to call recursively. process.nextTick() "starve" your I/O, which prevents the event loop from entering the poll phase.
Why is this situation included in Node.js? Since the design philosophy of Node.js is that an API should always be asynchronous even if it doesn't have to be, look at the following snippet
function apiCall(arg, callback) { if (typeof arg !== 'string') return process.nextTick( callback, new TypeError('argument should be string') ); }
The snippet does parameter checking and if incorrect, it passes the error to the callback. The API was recently updated to allow passing parameters to process.nextTick() allowing it to accept any parameters passed after the callback as arguments to the callback, so you don't have to nest functions.
What we are doing is passing the error back to the user, but only if we allow the rest of the user's code to execute. By using process.nextTick(), we ensure that apiCall() always runs its callback after the rest of the user code and before allowing the event loop to continue. To achieve this, the JS call stack is allowed to unwind and then the provided callback is immediately executed, which allows one to make recursive calls to process.nextTick() without hitting RangeError: Maximum call stack size exceeded as of v8.