Browser event loop
Execution stack
An execution environment for various functions. Before each function is executed, information about it is added to the execution stack. Before calling a function, create the execution environment and add it to the execution stack. After the function call, the execution environment is destroyed. Every time the JS engine executes, it executes the code at the top of the stack.
function f1 () {
f2();
}
function f2 () {
f3();
}
function f3() {
console.log('f3');
}
f1();
Copy the code
Relative to the execution stack:
Browser resident thread
- JS engine: Responsible for executing the top code on the stack
- GUI thread: Responsible for rendering the page
- Event listener thread: responsible for listening for various events
- Timer thread: Responsible for timing
- Network thread: Responsible for network communication
The event queue
Task classification: In JavaScript, functions are divided into two types, one is synchronous execution, and one is asynchronous execution.
For functions that execute asynchronously, such as sending network requests, timers, etc. Other threads in the browser are delegated to do the work for us, and when we’re done, the callback is put into the do event queue.
When the JS engine finds that there is nothing left in the execution stack, it adds the first function in the event queue to the execution stack.
Macro and micro tasks
Asynchronous tasks are divided into macro tasks and micro tasks. Correspondingly, there are two event queues, namely macro queue and micro queue.
The priority of microtasks is higher than that of macro tasks. When both queues are not empty, the tasks in the microqueue are executed first.
Macro tasks: Timer callbacks, Ajax, registered events, and basically all asynchronous operations are macro tasks.
Microtasks: Promise and MutationObserver
setTimeout(() = > console.log(1), 0);
new Promise(resolve= > {
resolve()
console.log(2)
}).then(() = > {
console.log(3)});console.log(4);
/ / 2
/ / 4
/ / 3
/ / 1
Copy the code
Node event loop
When Node.js starts, it initializes the event loop, processes the input script provided, and it may call some asynchronous API, schedule timers, or call process.Nexttick () to start processing the event loop.
The event loop is divided into six phases, each of which has a FIFO queue to perform the callback.
Typically, when an event loop enters a given phase, the callbacks in the queue for that phase are executed until the queue is exhausted or the maximum number of callbacks has been executed. When the queue is exhausted or the callback limit is reached, the event loop moves to the next phase.
Summary of stage
- timers: This phase has been executed
setTimeout()
和setInterval()
The scheduling callback function of. - Pending: system-level callback
- Idle, prepare: used only in the system
- poll: Performs I/ O-related callbacks (except for closed callback functions, those made by timers and
setImmediate()
Almost all callbacks outside of the scheduled) - check:
setImmediate()
This is where the callback function is executed. - close: Some closed callback functions such as:
socket.on('close', ...)
.
Between each running event loop, Node.js checks to see if it is waiting for any asynchronous I/O or timer, and shuts it down completely if it isn’t.
Details on stage
timers
There are two types of timers in Node: Immediate and Timeout, both of which are Node objects. Callbacks to Timeout class timers are executed at this stage.
Once the timer expires, the callback is invoked during the Timers phase of the next event cycle.
This type of timer can be created by setInterval and setTimeout in Node. The delay parameter is optional when creating this timer. If no value is provided or a value of 0 is specified, the default value is 1 millisecond.
Like browsers, timers are not accurate because they are not executed as soon as they expire, but only when they reach that stage.
pending
Some system-level callbacks will be performed during this phase. For example, when a TCP socket receives ECONNREFUSED while trying to connect, some * NIx systems want to wait to report an error.
poll
I/O callbacks are performed in this phase, such as fs.readfile and http.createserver, and this phase is the most frequently stuck phase in the Node event loop.
What the polling phase does:
- If the phase queue is not empty, the callback queue is looped through and executed synchronously until the queue is exhausted or a system-specific hard limit is reached
- If the queues in this phase are empty and the queues in other phases are not empty, the phase ends
- If the phase queue is empty and the other phase queues are also empty and blocked
const fs = require('fs');
const start = Date.now();
function sleep(n) {
const start = Date.now();
while (Date.now() - start < n);
}
setTimeout(() = > {
console.log('timeout'.Date.now() - start);
}, 100);
fs.readFile('./index.html'.() = > {
console.log('readFile'.Date.now() - start);
sleep(200);
});
// readFile 3
// timeout 208
Copy the code
check
Only the setImmediate() callback is executed during this phase. This enables some code to be executed as soon as the poll phase becomes idle.
The difference between setImmediate and setTimeout:
-
They belong to different queues
-
SetImmediate immediately adds the callback to the Checks queue, while setTimeout opens the timer thread and waits for the timer to expire
-
SetImmediate is more efficient than setTimeout
function test(fn, name) { let i = 0; console.time(name); const run = () = > { i++; if (i < 1000) { fn(run); } else { console.timeEnd(name); } } run(); } test(setTimeout.'setTimeout'); test(setImmediate, 'setImmediate'); / / setImmediate: 6.696 ms / / setTimeout: 1.698 s Copy the code
-
Timers are constrained by process performance, and the order in which the callbacks run is uncertain, depending on the state of the system at the time
setTimeout(() = > { console.log('timeout'); }, 0); setImmediate(() = > { console.log('immediate'); }); Copy the code
NextTick and Promise
Process.nexttick () is part of the asynchronous API, but technically not part of the event loop.
The nextTick queue and MicroTask columns must be emptied before each callback for each phase of an event loop is executed.
According to the language specification, the Promise object’s callback function enters the microtask queue in the asynchronous task, but the microtask queue is appended to the process.nextTick queue. And only when one queue is empty will another queue be emptied.
process.nextTick(() = > {
console.log(1);
process.nextTick(() = > {
console.log(2)})});Promise.resolve().then(() = > console.log(3));
process.nextTick(() = > console.log(4));
Promise.resolve().then(() = > console.log(5));
/ / 1
/ / 4
/ / 2
/ / 3
/ / 5
Copy the code