Pay attention to
In Node 11, Node’s Event Loop has become similar to the browser’s.
background
Event Loop is also a topic that js has always talked about. At the end of February, I read teacher Ruan Yifeng’s “Detailed Explanation of Node Timer”, and found that I could not completely standard the JS event cycle execution mechanism I had seen before. I also looked up some other information and made notes. I felt inappropriate and summarized it into a paper.
Event loops and execution mechanisms are different in browsers and Nodes and should not be confused. The browser Event loop is a specification defined in HTML5, while node is implemented by the Libuv library. At the same time, when I read the book “Simple nodeJs”, I found that the node mechanism has been different at that time, so the node part of this article is for the version published in this article. I highly recommend reading the first three articles in the reference link.
Browser environment
Js is executed as a single thread (regardless of web workers) and all code is executed on the main thread call stack. Tasks in the task queue are polled only when the main thread is empty.
Task queue
Asynchronous tasks are classified into tasks (macrotasks, also known as macrotasks) and microtasks (microtasks). Task queues (Macrotask Queues) and MicroTask queues are called task queues (Macrotask Queues) and microTask queues (Macrotask Queues).
- Task: Script code, setTimeout, setInterval, I/O, UI render.
- Microtask: Promise, Object.observe, MutationObserver.
The specific process
- Completes the task in the main execution thread.
- Remove tasks from the Microtask Queue and execute them until they are empty.
- Retrieve a task from the Macrotask Queue.
- Remove tasks from the Microtask Queue and execute them until they are empty.
- Repeat 3 and 4.
That is, for synchronous completion, one macro task, all microtasks, one macro task, all microtasks……
Pay attention to
- In the browser page, it can be considered that there is no code in the initial execution thread, and the code in each script tag is an independent task, that is, the microtask created in the previous script will be executed before the synchronization code in the later script is executed.
- If microTasks are added all the time, microTask execution continues, and MacroTask is “stuck.”
- The execution sequence of some browsers is inconsistent with the above, which may be inconsistent with the standards or conflicts between JS and HTML standards. You can read the first article in the Resources article.
New Promise((resolve, reject) =>{console.log(' sync '); Resolve ()}).then(() => {console.log(' async ')})
, i.e.,promise
thethen
andcatch
It is MicroTask, not its own internal code.- Individual browser-specific apis are not listed.
Pseudo code
while (true) {macro task queue. Shift () microtask queue all tasks ()}Copy the code
The node environment
Js is executed as a single thread, and all code is executed in the main thread call stack. Tasks in the task queue are polled only when the main thread is empty.
Cycle stages
Each cycle of events in Node is divided into six phases in order, from the implementation of Libuv:
- Timers: Executes setTimeout and setInterval callbacks that meet the conditions.
- I/O callbacks: Whether there are callbacks to completed I/O operations, poll residue from the previous round.
- Idle, prepare: can be ignored
- Poll: Indicates that the wait for unfinished I/O events ends due to timers and the timeout period.
- Check: Executes the setImmediate callback.
- Close callbacks: Close all closing handles, some onclose events.
Enforcement mechanism
Several queue
In addition to the task types in the loop phase above, we are left with microtasks shared by browsers and Nodes, and Process. nextTick, which is unique to Node, which we call microTask Queue and nextTick Queue.
We also call the execution queues of several stages of the loop as Timers Queue, I/O Queue, Check Queue, and Close Queue respectively.
Before the loop
Before entering the first loop, the following is done:
- Synchronization task
- Make an asynchronous request
- This section describes how to set the timer validity time
- perform
process.nextTick()
Start cycle
Execute each of the six phases of the cycle, clearing the NextTick Queue and the Microtask Queue. Then the next stage is executed. After the completion of all six stages, the next cycle is entered. That is:
- Clear Timers Queue, NextTick Queue, and Microtask Queue in the current loop.
- Clear the I/O Queue, NextTick Queue, and Microtask Queue in the current loop.
- Clear the Check Queu, NextTick Queue, and Microtask Queue in the current loop.
- Clear the Close Queu, NextTick Queue, and Microtask Queue in the current loop.
- Enter the next cycle.
As you can see, nextTick has a higher priority than Microtasks such as Promise. SetTimeout and setInterval have a higher priority than setImmediate.
Pay attention to
- If created during the timers phase
setImmediate
Is executed during the Check phase of this cycle, if created during timerssetTimeout
Because the timers have been removed, the system enters the next cycle. The same is true for creating timers in the Check phase. setTimeout
Priority thansetImmediate
High, but becausesetTimeout(fn,0)
The true delay of the can not be exactly 0 seconds, may appear first createdsetTimeout(fn,0)
And thansetImmediate
Is executed after the callback.
Pseudo code
while (true) {loop. ForEach ((phase) => {phase all tasks () nextTick all tasks () microTask all tasks ()}) loop = loop.next}Copy the code
The test code
function sleep(time) {
let startTime = new Date()
while (new Date() - startTime < time) {}
console.log('1s over')}setTimeout(() => {
console.log('setTimeout - 1')
setTimeout(() => {
console.log('setTimeout - 1 - 1')
sleep(1000)
})
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 1 - then')
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 1 - then - then')
})
})
sleep(1000)
})
setTimeout(() => {
console.log('setTimeout - 2')
setTimeout(() => {
console.log('setTimeout - 2 - 1')
sleep(1000)
})
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 2 - then')
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 2 - then - then')
})
})
sleep(1000)
})
Copy the code
- Browser output:
setTimeout-1 //1 indicates a single task 1s oversetTimeout - 1 - then setTimeout - 1 - then - then setTimeout-2 //2 indicates a single task 1s oversetTimeout - 2 - then setTimeout - 2 - then - then setTimeout - 1 - 1 1s over setTimeout - 2 - 1 1s over Copy the code
- The node output:
setTimeout - 1 1s over setTimeout-2 //1 and 2 are single-phase task 1s oversetTimeout - 1 - then setTimeout - 2 - then setTimeout - 1 - then - then setTimeout - 2 - then - then setTimeout - 1 - 1 1s over setTimeout - 2 - 1 1s over Copy the code
This also shows the difference between the event loop in the browser and node.
The execution of the new Node is the same as that of the browser. In the browser environment, the console output is used as an example to refer to the function. The execution process is as follows
<! -- Completes the task in the main execution thread. -- > <! Remove tasks from the Microtask Queue until they are empty. -- > <! Retrieve a task from the Macrotask Queue. -- > <! Remove tasks from the Microtask Queue until they are empty. -- > <! Repeat 3 and 4. --> IQ refers to micro task queue, AQ refers to macro task queue 1. Finish executing tasks in the main thread: The main thread has finished executing,setTimeout - 1.setTimeout-2 Enter wait 2. Clear IQ: there is no task in IQ 2. Perform a mission in AQ:setWhen timeout-1 reaches the time, it enters AQ and is executedsetTimeout-1-1 Enters the wait state,setTimeout-1-then directly enters the IQ queue becausesetTimeout-1 waits for 1ssetTimeout-2 must have entered AQ,setQ: [setTimeout - 1 - then], AQ:setTimeout-2, setTimeout-1-1] 3. Clear IQ: the IQ has been clearedsetTimeout - 1 - then, executionsetTimeout-1-then, during execution,setTimout-1-then-then is added directly to IQ, so IQ is not empty, so continue executionsetTimout-1-then-then, IQ is cleared, and the end state is IQ: [], AQ: [setTimeout-2, setTimeout-1-1] 4. Execute a task in AQ: executesetTimeout-2 5. Clear IQ: This step is similar to 3, so outputsetTimeout - 2 - then,setIQ: [], AQ: [setTimeout-1-1, setTimeout-2-1] 6. Perform a task in AQ: i.esetTimeout-1-1 7. Empty IQ: empty itself 8. Perform a task in AQ: i.esetTimeout-2-1
Copy the code
Refer to the article
- Tasks, Microtasks, Queues and schedules are highly recommended
- Don’t confuse NodeJS with event loops in browsers highly recommended
- The Event module in Node is highly recommended
- Understanding the Cycle of Events 1 (Analysis)
- Node timer description