A 10-minute article teaches you Event loop - Browser and Node

Event Loop is a very important concept, which refers to an operating mechanism of a computer system.

The JavaScript language uses this mechanism to solve some problems caused by single-threaded operation.

A 10-minute article teaches you Event loop - Browser and Node

If you want to understand the Event Loop, you must start with the running mode of the program. The program after running is called a "process", and in general, a process can only perform one task at a time.

If there are many tasks to perform, there are three solutions.

(1) Queue. Because a process can only execute one task at a time, it has to wait for the previous task to complete before executing the subsequent task.

(2) Create a new process. Use the fork command to create a new process for each task.

(3) Create a new thread. Because the process is too resource-intensive, today's programs often allow a process to contain multiple threads, which are used to complete tasks. (For a detailed explanation of processes and threads, see here.)

Taking the JavaScript language as an example, it is a single-threaded language, and all tasks are completed on one thread, that is, the first method above is adopted. Once encountering a large number of tasks or encountering a time-consuming task, the web page will appear "suspended dead", because the JavaScript cannot stop and cannot respond to the user's behavior.

You may ask, why is JavaScript single-threaded, can't it be implemented as multi-threaded?

This has something to do with history. JavaScript has been single-threaded since its inception. The reason is probably that you don't want to make the browser too complicated, because multiple threads need to share resources and may modify each other's running results, which is too complicated for a web scripting language. Later, it was established that JavaScript is a single-threaded language. (The Worker API can be multithreaded, but JavaScript itself is always single-threaded.)

If a task is time-consuming, such as involving a lot of I/O (input/output) operations, then the running of the thread may look like the following.

A 10-minute article teaches you Event loop - Browser and Node

The green part of the above figure is the running time of the program, and the red part is the waiting time. It can be seen that most of the running time of this thread is waiting for the return result of the I/O operation because the I/O operation is very slow. This mode of operation is called "synchronous I/O" or "blocking I/O".

If you use multithreading and run multiple tasks at the same time, it is likely to be as follows.

A 10-minute article teaches you Event loop - Browser and Node

The above figure shows that multithreading not only occupies many times of system resources, but also idles many times of resources, which is obviously unreasonable.

Event Loop is proposed to solve this problem. Wikipedia defines it this way:

"Event Loop is a programming construct that waits for and dispatches events or messages in a program."

Simply put, it is to set up two threads in the program: one is responsible for the operation of the program itself, called the "main thread"; the other is responsible for the communication between the main thread and other processes (mainly various I/O operations), called "Event Loop thread" (can be translated as "message thread").

A 10-minute article teaches you Event loop - Browser and Node

The green part of the main thread in the above figure still represents the running time, while the orange part represents the idle time. Whenever I/O is encountered, the main thread will let the Event Loop thread notify the corresponding I/O program, and then run it backwards, so there is no red waiting time. When the I/O program completes the operation, the Event Loop thread returns the result to the main thread. The main thread calls the preset callback function to complete the entire task.

As you can see, due to the extra orange idle time, the main thread is able to run more tasks, which improves efficiency. This mode of operation is called "asynchronous I/O" or "non-blocking mode".

This is exactly how the JavaScript language works. Although the single-threaded model poses a great limitation to JavaScript, it also gives it advantages that other languages ​​do not have. If deployed well, JavaScript programs will not be blocked, which is why the node.js platform can cope with large traffic access with very few resources.

In practice, understanding the meaning of event loop can help you analyze some asynchronous order problems (of course, with the popularity of es7 async and await, such opportunities are getting less and less). In addition, it also has a positive effect on your understanding of the internal mechanism of browsers and Node ; for interviews , when asked about the execution order of a bunch of asynchronous operations, you will not be blinded.

3. Implementation on the browser

In JavaScript, tasks are divided into Task (also known as MacroTask, macro task) and MicroTask (micro task). They respectively contain the following:

MacroTask : script (overall code), setTimeout, setInterval, setImmediate (node ​​only), I/O, UI rendering

MicroTask : process.nextTick (node ​​only), Promises, Object.observe (discarded), MutationObserver

One thing to note is that in the same context, the total execution order is synchronous code—>microTask—>macroTask [6]. We will talk about this later.

In the browser, there are many task queues (task queues) from different task sources in an event loop, and the tasks in each task queue are executed in strict accordance with the first-in, first- out order. However, because of the browser's own scheduling relationship, the execution order of tasks in different task queues is uncertain .

Specifically, the browser will continuously fetch tasks from the task queue for execution in order. After each task is executed, it will check whether the microtask queue is empty (the specific sign of executing a task is that the function execution stack is empty) . Empty will execute all microtasks at one time . Then enter the next loop to take the next task from the task queue for execution, and so on.

A 10-minute article teaches you Event loop - Browser and Node

Note: The orange MacroTask task queue in the figure should also be constantly being switched.

This paragraph refers to the relevant content of "What is the browser's event loop (Event Loop)" in large numbers. If you want to see a more detailed description, you can use it yourself.

4. Implementation on Node

The event loop of nodejs is divided into 6 stages, which will run repeatedly in sequence, as follows:

  1. timers: Execute callbacks that expire in setTimeout() and setInterval().

  2. I/O callbacks: A small number of I/O callbacks in the previous round of loops will be delayed until this stage of the round.

  3. idle, prepare: the movement of the queue, only used internally

  4. poll: The most important stage, executing the I/O callback, will block in this stage under appropriate conditions

  5. check: execute the callback of setImmediate

  6. close callbacks: execute the callback of the close event, such as socket.on("close",func)

Unlike the browser, the microTask queue is executed after each stage is completed, not after the MacroTask task completes. This leads to different results for the same code in different contexts . We will discuss below.

In addition, it should be noted that if setImmediate is created during the execution of the timers stage, it will be executed in the check stage of this cycle. If setTimeout is created in the timers stage, since the timers have been taken out, it will enter the next round of loops, and the check stage will be created. The timers task is the same.

A 10-minute article teaches you Event loop - Browser and Node

5. Example

5.1 The difference between browser and Node execution order

setTimeout(()=>{ console.log('timer1') Promise.resolve().then(function() { console.log('promise1') })}, 0)setTimeout(()=>{ console.log('timer2') Promise.resolve().then(function() { console.log('promise2') })}, 0)浏览器输出:time1promise1time2promise2Node输出:time1time2promise1promise2

In this example, Node's logic is as follows:

Initially timer1 and timer2 are in the timers phase. At the beginning, first enter the timers stage, execute the callback function of timer1, print timer1, and put the promise1.then callback into the microtask queue, execute timer2 in the same steps, and print timer2;

At this point, the execution of the timer phase ends, and before the event loop enters the next phase, all tasks in the microtask queue are executed, and promise1 and promise2 are printed in turn.

The browser outputs timer1, promise1 first, and then timer2, promise2 because the two setTimeouts are used as two MacroTasks.

For more detailed information, please refer to "In-depth understanding of js event loop mechanism (Node.js)"

To prove our theory, change the code to look like this:

setImmediate(() => { console.log('timer1') Promise.resolve().then(function () { console.log('promise1') })})setTimeout(() => { console.log('timer2') Promise.resolve().then(function () { console.log('promise2') })}, 0)Node输出:timer1 timer2promise1 或者 promise2timer2 timer1promise2 promise1

It stands to reason

setTimeout(fn,0)

should be better than

setImmediate(fn)

Quick, there should be only the second result, why are there two results?

This is because Node can't do 0 milliseconds, at least 1 millisecond. In actual execution, after entering the event loop, it may be 1 millisecond, or it may not be 1 millisecond, depending on the current situation of the system. If it is less than 1 millisecond, the timers phase will be skipped, and the check phase will be entered, and the callback function of setImmediate will be executed first.

Also, if the Timer phase has passed, then setImmediate will be faster than setTimeout, for example:

const fs = require('fs');fs.readFile('test.js', () => { setTimeout(() => console.log(1)); setImmediate(() => console.log(2));});

The above code will first enter the I/O callbacks stage, then the check stage, and finally the timers stage. Therefore, setImmediate will be executed earlier than setTimeout.

For details, see "Node Timer Details".

5.2 The execution speed of different asynchronous tasks

setTimeout(() => console.log(1));setImmediate(() => console.log(2));Promise.resolve().then(() => console.log(3));process. nextTick(() => console.log(4)); output result: 4 3 1 2 or 4 3 1 2

Because we said above that microTask will run better than macroTask, so output the following two first, and in Node process.nextTick has priority over Promise [3], so 4 is before 3. According to what we said before, Node does not have 0ms in the absolute sense, so the order of 1 and 2 is not fixed.

5.3 MicroTask queue and MacroTask queue

setTimeout(function () { console.log(1); },0); console.log(2); process.nextTick(() => { console.log(3); }); new Promise(function (resolve, rejected) { console.log(4); resolve() }).then(res=>{ console.log(5); }) setImmediate(function () { console.log(6) }) console.log('end');Node输出:2 4 end 5 1 6

This example is from "Execution Mechanisms in JavaScript". The code of Promise is synchronous code, then and catch are asynchronous, so 4 should be output synchronously, then the then of Promise is located in the microTask, which is better than other tasks located in the macroTask queue, so 5 will be better than 1, 6 output, and Timer is better than Check stage, so 1,6.

In summary, regarding the most critical order, we have to follow the following rules :

  1. In the same context, MicroTask will run before MacroTask

  2. Then the browser runs in order of one MacroTask, all MicroTasks, Node runs in the order of six stages, and after each stage runs a queue of MicroTasks

Process.tick() will be better than Promise under the same MicroTask queue

Those who are interested in the front-end and want to learn can add me QQ skirt: 213126486 Invitation code: Leaves, discuss progress together~

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325431427&siteId=291194637