The eventloop mechanism is key to Javascript's implementation of pseudo-"multithreading." This part is also a question that is often asked in interviews, take a note for reference.
The execution model of JavaScript
To be clear, JavaScript is a single-threaded language. All tasks in JavaScript are divided into one of three categories:
Synchronization tasks
Asynchronous macro tasks
Asynchronous microtasks
JavaScript implements asynchronous tasks by frequently switching between these tasks.
JavaScript's task execution mechanism
In terms of the overall execution mechanism, JavaScript handles the task at hand in the following order:
If it is a synchronous task, it is executed directly in the main thread.
If it is an asynchronous task, register the function in the Event Table, and when the asynchronous event completes (I/O, timeout, etc.), move the one-step callback function into the Event Queue.
When the main thread task finishes executing, start executing the asynchronous callback task in the Event Queue (the same EventLoop is always the first microtask and then the macro task) until the execution is completed.
This cycle is called Event Loop.
Macro tasks? Microtasks?
How to distinguish
Macro tasks
setTimeout, setInterval, setImmediate
Microtasks
Native Promise
process.nextTick(node.js)
Object.observe
MutationObserver
Enforcement mechanisms
Asynchronous events in an Event Queue are executed according to the following flowchart:
Asynchronous task execution process
First take a macro task from the macro task queue in the Event Queue and execute it.
After each macro task is executed, check the microtask queue and execute all microtasks.
First, execute the synchronization task console.log (start) and output start.
When setTimeout is encountered, put the callback into the macro task queue.
When a new promise is encountered, immediately execute the console.log (promise) output the promise, and put two .then callback functions into the microtask queue.
Continue with the synchronization task console.log(end) and output end.
After the synchronization task is executed, execute the microtask console.log(then1, then2), and output then1, then2.
Finally, execute the macro task console.log (setTimeout) and output setTimeout.
Output: start promise1 end then1 then2 setTimeout promise2 then3
Interpretation:
First, execute the synchronization task console.log (start) and output start.
When setTimeout is encountered, put the callback into the macro task queue.
When a new promise is encountered, immediately execute console.log(promise1) to output promise1, and put two .then callback functions into the microtask queue.
Continue with the synchronization task console.log(end) and output end.
After the synchronization task is executed, execute the microtask console.log(then1, then2), and output then1, then2.
Execute the macro task console.log (setTimeout) and output setTimeout.
Execute the new promise from the previous macro task, immediately execute the console.log ('promise 2'), output promise 2, and put .then into the microtask queue.
Execute the microtask console.log (then3) and output then3.
First, execute the synchronization task console.log(1) and output 1.
When setTimeout is encountered, put the callback into the macro task queue.
When you encounter process.nextTick, put the function in the microtask queue.
When a new Promise is encountered, immediately execute console.log(7) output 7, and put the .then callback function into the microtask queue.
When setTimeout is encountered, put the callback into the macro task queue.
After the synchronization task is executed, all microtasks are executed sequentially, with output 6,8.
Execute the firstsetTimeout macro task console.log(2) and output 2.
When you encounter process.nextTick, put the function in the microtask queue.
When a new promise is encountered, immediately execute the console.log(4) output 4, and put the .then callback function into the microtask queue.
After the macro task is executed, all microtasks are executed sequentially, with an output of 3,5.
Execute the secondsetTimeout macro task console.log(9) and output 9.
When you encounter process.nextTick, put the function in the microtask queue.
When a new Promise is encountered, immediately execute the console.log(11) output 11, and put the .then callback function into the microtask queue.
When the macro task is executed, all microtasks are executed sequentially, with output 10, 12.
Postscript
Recently encountered a related issue while dealing with react-redux's useSelector and useState hooks combined. Multiple setStates in the same useEffect, and the initial state and final state coincide, do not trigger the corresponding dependencies in other hooks. After using the new Promise(resolve=> setTimeout(resolve, 0)), the problem no longer occurs, which triggers the motivation to write this note. Speculation may be related to React's reconciliation mechanism, and it is necessary to dig deeper into React's code.