This article will let you explore why the browser and Node.js are designing EventLoop in this way. I hope it will be helpful to everyone!
Event Loop is a basic concept of JavaScript. It is a must-ask in interviews and is often talked about in daily life. But have you ever thought about why there is Event Loop and why it is designed like this? Woolen cloth?
Today we will explore the reasons.
JavaScript is used to implement web page interaction logic, involving dom operations. If multiple threads operate at the same time, synchronization and mutual exclusion are required. The processing is designed to be single-threaded for simplicity. However, if it is single-threaded, it will be blocked when encountering timing logic and network requests. How to do it?
You can add a layer of scheduling logic. Just encapsulate the JS code into tasks and put them in a task queue, and the main thread will continue to fetch tasks and execute them.
Every time a task is executed, a new call stack will be created.
Among them, timers and network requests are actually executed in other threads. After execution, a task is placed in the task queue to tell the main thread to continue. Executed.
Because these asynchronous tasks are executed in other threads, and then notified to the main thread through the task queue, it is an event mechanism, so this loop is called Event Loop.
These asynchronous tasks executed in other threads include timers (setTimeout, setInterval), UI rendering, and network requests (XHR or fetch).
However, the current Event Loop has a serious problem. There is no concept of priority. It is only executed in order. If there are high-priority tasks, they will not be executed in time. Therefore, a queue-jumping mechanism must be designed.
Then just create a high-priority task queue. After each ordinary task is executed, all high-priority tasks are executed, and then the ordinary tasks are executed.
#With the queue jumping mechanism, high-quality tasks can be executed in a timely manner.
This is the current browser Event Loop.
The ordinary tasks are called MacroTask (macro tasks), and the high-quality tasks are called MicroTasks (micro tasks).
Macro tasks include: setTimeout, setInterval, requestAnimationFrame, Ajax, fetch, script tag code.
Microtasks include: Promise.then, MutationObserver, Object.observe.
How to understand the division of macro and micro tasks?
Timers and network requests are common asynchronous logic that notifies the main thread after other threads have finished running, so they are all macro tasks.
The three types of high-quality tasks are also easy to understand. MutationObserver and Object.observe both monitor changes in an object. Changes are very instantaneous. You must respond immediately, otherwise it may change again. Yes, Promise organizes asynchronous processes, and calling then at the asynchronous end is also very good.
This is the design of Event Loop in the browser: The Loop mechanism and Task queue are designed to support asynchronous and solve the problem of logic execution blocking the main thread. The queue jumping mechanism of the MicroTask queue is designed to solve the problem of high The problem of executing optimal tasks as early as possible.
But later, the execution environment of JS was not only a browser, but also Node.js, which also had to solve these problems, but the Event Loop it designed was more detailed.
Node is a new JS running environment, which also supports asynchronous logic, including timers, IO and network requests, obviously, can also be run using Event Loop.
However, the browser's Event Loop is designed for browsers. For high-performance servers, the design is still a bit rough.
Where is it rough?
The browser's Event Loop is only divided into two levels of priority, one is macro tasks and the other is micro tasks. But there is no further prioritization between macro tasks, and there is no further prioritization between micro tasks.
And Node.js task macro tasks also have priorities. For example, the logic of the timer has a higher priority than the logic of IO, because when it comes to time, the earlier the more accurate it is; and the close resource The priority of processing logic is very low, because if it is not closed, it will occupy more resources such as memory and have little impact.
So the macro task queue was split into five priority levels: Timers, Pending, Poll, Check, and Close.
Explain these five macro tasks:
Timers Callback: When it comes to time, the earlier the execution, the more accurate it will be, so this has the highest priority and is easy to understand.
Pending Callback: Callback when handling network, IO and other exceptions. Some *niux systems will wait for errors to be reported, so they must be handled.
Poll Callback: Processing IO data and network connection, this is what the server mainly handles.
Check Callback: The callback for executing setImmediate. The characteristic is that this can be called back just after the IO is executed.
Close Callback: Callback to close the resource. Delayed execution will not affect it and has the lowest priority.
So, the Event Loop of Node.js runs like this:
There is another difference that you should pay special attention to:
The Event Loop of Node.js is not like the browser that executes one macro task at a time, and then executes all micro tasks. Instead, it executes a certain number of Timers macro tasks, then executes all micro tasks, and then executes a certain amount. number of Pending macro tasks, and then execute all micro tasks. The same is true for the remaining Poll, Check, and Close macro tasks. (Correction: This was the case before node 11. After node 11, it was changed to each macro task executing all micro tasks)
Why is this?
In fact, it is easy to understand according to the priority:
Assume that the priority of the macro task in the browser is 1, so it is executed in order, that is, one macro task, all A microtask, then a macrotask, then all microtasks.
The macro tasks of Node.js also have priorities, so Node.js’s Event Loop will allocate all tasks with the current priority each time. After the macro task is finished, run the micro task, and then run the next priority macro task.
That is, a certain number of Timers macro tasks, then all micro tasks, then a certain number of Pending Callback macro tasks, and then all micro tasks.
Why is it said to be a certain amount?
Because if there are too many macro tasks in a certain stage, the next stage will never be executed, so there is an upper limit, and the remaining next Event Loop will continue to be executed.
In addition to macro tasks having priorities, micro tasks are also prioritized. There is an additional high priority micro task of process.nextTick, which runs before all ordinary micro tasks.
So, the complete process of Node.js Event Loop is like this:
Compared with the Event Loop in the browser, it is obviously much more complicated, but after our previous Through the analysis, you can also understand:
Node.js has prioritized macro tasks. From high to low, they are Timers, Pending, Poll, Check, and Close. It also prioritizes micro tasks. The tasks are divided into nextTick microtasks and other microtasks. The execution process is to first execute a certain number of macrotasks of the current priority (leaving the rest for the next cycle), then execute the microtasks of process.nextTick, then execute ordinary microtasks, and then execute a certain number of the next priority. number of macro tasks. . This cycle continues. There is also an Idle/Prepare stage for Node.js internal logic, so you don’t need to worry about it.
Changed the way of executing one macro task at a time in the browser Event Loop, which allows high-priority macro tasks to be executed earlier, but also sets an upper limit to prevent the next stage from not being executed. .
There is another point to pay special attention to, which is the poll stage: If it is executed to the poll stage and it is found that the poll queue is empty and there are no tasks to be executed in the timers queue and check queue, then it will be blocked and so on. Wait for IO events here instead of idling. This design is also because the server mainly handles IO, and blocking here can respond to IO earlier.
The complete Node.js Event Loop is like this:
Compare the browser's Event Loop:
The overall design ideas of the Event Loop of the two JS running environments are similar, except that the Event Loop of Node.js has a more fine-grained division of macro tasks and micro tasks, which is also easy to understand. After all, Node The environment for .js is different from that of browsers. More importantly, the performance requirements of the server will be higher.
JavaScript was first used to write web page interaction logic. In order to avoid the synchronization problem of multiple threads modifying the DOM at the same time, it was designed to be single-threaded. To solve the single-thread blocking problem, a layer of scheduling logic is added, that is, Loop loop and Task queue, and the blocking logic is placed in other threads to run, thus supporting asynchronous operation. Then in order to support high-priority task scheduling, micro-task queues were introduced. This is the browser's Event Loop mechanism: execute one macro task at a time, and then execute all micro tasks.
Node.js is also a JS running environment. If you want to support asynchronous, you also need to use Event Loop. However, the server environment is more complex and has higher performance requirements, so Node.js has both macro and micro tasks. Made a more fine-grained priority classification:
Node.js is divided into 5 types of macro tasks, namely Timers, Pending, Poll, Check, and Close. Two types of microtasks are divided, namely process.nextTick microtasks and other microtasks.
The Event Loop process of Node.js is to execute a certain number of macro tasks in the current stage (the rest will be executed in the next loop), and then execute all micro tasks. There are Timers, Pending, Idle/ Prepare, Poll, Check, Close 6 stages. (Correction: This was the case before node 11. After node 11, it was changed to each macro task to execute all micro tasks)
The Idle/Prepare stage is used internally by Node.js, so don’t worry about it.
Special attention should be paid to the Poll stage. If the poll queue is empty and the timers and check queues are also empty, it will be blocked here waiting for IO until there is a callback in the timers and check queues. Continue the loop again.
Event Loop is a set of scheduling logic designed by JS to support asynchronous and task priority. It has different designs for different environments such as browsers and Node.js (mainly the division of task priorities) (different granularity), Node.js faces more complex environments and has higher performance requirements, so the Event Loop design is more complex.
For more node-related knowledge, please visit: nodejs tutorial! !
The above is the detailed content of Discover why browsers and Node.js designed EventLoop this way!. For more information, please follow other related articles on the PHP Chinese website!