New knowledge storeFront end from entry to groundFor attention, star and suggestions, update from time to time ~
Related series: A Journey of Front-end Foundation from Scratch
Javascript is a single-threaded language. Web-worker was proposed in the latest HTML5, but the core that javascript is single-threaded remains unchanged. No matter who writes the code, it has to be executed sentence by sentence.
When we open a website, the rendering process of a page involves a number of tasks, such as rendering page elements. Script The execution of a script, such as loading pictures and music through a network request. If the tasks are executed one by one and take too long, stalling will occur. Hence the Event Loop.
What is an Event Loop?
Event loops can be understood as a way to achieve asynchrony. Event loop definition in HTML Standard:
To coordinate events, user interactions, scripting, rendering, networking, etc., the user agent must use the Event loop described in this section.
JavaScript has one main thread, the main thread, and a call-stack, also known as the execution stack. All tasks are placed on the call stack and wait for the main thread to execute them. The task to be performed is the raw material on the assembly line, only after the first one is finished, the last one can be carried out. Event Loops are the workers who put the ingredients on the assembly line, coordinating user interaction, scripting, rendering, and networking.
The tasks to be performed are divided into two types:
- Synchronization task
- Asynchronous tasks
The main thread executes all code from top to bottom
- Synchronous tasks are executed directly into the main thread, while asynchronous tasks are executed directly into the main thread
Event Table
And register the corresponding callback function - When the specified condition (asynchronous task completion) is met,
Event Table
I’m going to move this function inEvent Queue
- When the main thread task is finished, the
Event Queue
To read the task, enter the main thread to execute. - This repeated process is known as an Event Loop.
Task Queue
An Event loop has one or more task queues. When the user agent schedules a task, it must be added to a TSAK queue in the corresponding Event loop.
A task, also known as a macrotask, is a first-in, first-out queue that is served by a specified task source.
Task task sources are very broad. To sum up, task task sources include:
- setTimeout
- setInterval
- setImmediate
- I/O
- UI rendering
- Overall code script
So a Task Queue is a Queue of tasks. The Event Loop in JavaScript is constantly coming up to this queue and asking if any tasks are available to run.
Micro tasks (microtask)
Each event loop has a microtask queue, and a microtask is placed in a microTask queue rather than a task queue.
A MicroTask queue is similar to a task queue in that it is a first-in, first-out queue and the task is provided by the specified task source. The difference is that there is only one MicroTask queue in an Event loop.
Microtask tasks are generally considered to have the following sources:
- process.nextTick
- promises
- Object.observe
- MutationObserver
In the Notes 3.1 Promises/A + specification mentioned promise then method can be used in A “macro (macro – task) task” mechanism or “micro task (micro – task)” mechanism to realize. So the implementation of promises may differ between browsers.
Event Loop in the browser environment
The order of the event loop determines the execution order of the JS code. After entering the overall code (macro task), the first loop begins. Then perform all the microtasks. Then start from the macro task again, find one of the task queues to complete, and then execute all the microtasks.
After executing tasks in the MicroTask queue, it is possible to render updates. (Browsers are smart, they don’t respond immediately to multiple DOM changes within a frame, but instead accumulate changes to update the view at up to 60HZ)
setTimeout
As shown in the following code, setTimeout is an asynchronous task,
console.log('start')
setTimeout(() = >{
console.log('setTimeout')});console.log('end');
Copy the code
- The main thread performs a synchronization task:
console.log('start');
- encounter
setTimeout
When an asynchronous task is discovered, an asynchronous callback is registered - Execute the statement
console.log('end')
- Main thread task completed. Look
Event Queue
Whether the task is waiting to be executed, as long as the main threadtask queue
There are no more tasks to execute, and the main thread is just waiting here - The asynchronous task is executed when the waiting time is up
console.log('setTimeout')
.
The js engine has a monitoring process that continuously checks to see if the main thread stack is empty, and if it is, checks the Event Queue to see if there are any functions waiting to be called.
Note that the Event Queue is not checked for tasks pending execution until the main thread has finished executing, so a different situation may occur.
console.log('start')
setTimeout(() = >{
console.log('setTimeout')},3000);
todo(); // Suppose this is an operation that takes 10 seconds
Copy the code
Normally, console output should look like this
start
// Wait 3 seconds
setTimeout
Copy the code
In reality, the output looks something like this:
start
// Wait 10 seconds
setTimeout
Copy the code
Re-analyze the execution process:
- The main thread performs a synchronization task:
console.log('start');
- encounter
setTimeout
When an asynchronous task is discovered, an asynchronous callback is registered - Execute the statement
todo()
- Three seconds is up. Time event
timeout
The printing task enters the Event Queue - Main thread task completed. Look
Event Queue
Whether tasks are waiting to be executed, - perform
console.log('setTimeout')
.
The setTimeout function adds the task to the Event Queue after a specified time. Unlike the previous one, when the timed Event is complete, the main thread task is not finished. The Event Queue will not be queried until the main thread has finished executing this round of code. So, wait about 10 seconds for the second output from the console.
setTimeout(fn,0)
SetTimeout (fn,0) specifies that a task will be executed at the earliest available free time on the main thread, which means that it will be executed as soon as all synchronization tasks in the main thread execution stack are completed and the stack is empty.
Even if the main thread is empty, 0 milliseconds is actually not reachable. According to HTML standards, the minimum is 4 milliseconds.
setInterval
SetInterval will place the registered functions in the Event Queue at specified intervals. If the previous task takes too long, it will also need to wait.
Similar to setTimeout, for setInterval(fn,ms), fn is not executed once every ms seconds, but every ms seconds, fn enters the Event Queue. If the callback fn of **setInterval executes at a time when the main thread is busy and exceeds the delay ms, it does not appear to have a time interval at all, but will execute continuously. 六四运动
Promise and process. NextTick (the callback)
Process.nexttick (callback) similar to “setTimeout” in Node.js, the callback function is called in the next loop of the event loop.
Take a piece of code as an example:
setTimeout(function() {
console.log('setTimeout');
})
new Promise(function(resolve) {
console.log('promise');
}).then(function() {
console.log('then');
})
console.log('console');
Copy the code
The main thread executes all code from top to bottom
- To meet a
setTimeout
Then register its callback function and distribute it to the macro task Event Queue. - And then I ran into
Promise
.new Promise
Execute immediately,then
The function is distributed to the microtask Event Queue. - encounter
console.log()
Immediately. - The whole script is executed as the first macro task. What are the microtasks? We found that
then
In the microtask Event Queue, execute. - Ok, the first round of the Event loop is over, so let’s start the second round, starting, of course, with the macro task Event Queue. We found the macro task Event Queue
setTimeout
The corresponding callback function is executed immediately. - The end.
Macro and micro tasks are nested
After a macro task is executed, all microtasks are executed, and then a macro task is executed
console.log('start');
Promise.resolve()
.then(function promise1() { // then1
console.log('promise1');
})
.then(function () { // then2
console.log('promise2')})setTimeout(function setTimeout1() { // setTimeout1
console.log('setTimeout1')
Promise.resolve().then(function promise2() { // then3
console.log('promise3'); })},0)
setTimeout(function setTimeout2() { // setTimeout1
console.log('setTimeout2')},0)
console.log('end')
Copy the code
Analyze the execution process:
- encounter
console.log()
, execute immediately, and print start. - encounter
Promise
.then
Is distributed to the microtask Event Queue. We remember tothen1
. - encounter
setTimeout
The callback function is distributed to the macro task Event Queue. Let’s call that thetasetTimeout1
. - Meet again
setTimeout
, whose callback function is distributed to the macro task Event Queue, denoted assetTimeout2
. - encounter
console.log()
, execute immediately, and print end.
At the end of the first round of execution, the console outputs stert, end. At this point, the task queue is as follows:
Macro task Event Queue | Microtask Event Queue |
---|---|
setTimeout1 | then1 |
setTimeout2 |
Performing microtasks:
- Execute then1, output promise1, and then is distributed to the microtask, denoted then2
- In this case, it is a microtask loop that executes HTEN2 and outputs promise2
When the microtask is complete, the second cycle begins, and the macro task setTimeout1 is switched to:
- encounter
console.log()
, execute immediately and print setTimeout1. - encounter
Promise
.then
Is distributed to the microtask Event Queue. We remember tothen3
.
Perform the microtask THEN3:
- encounter
console.log()
, execute immediately, and output promise3.
The third cycle begins with the macro task setTimeout2
- encounter
console.log()
, execute immediately and print setTimeout2
Finally, the console output is
stert
end
promise1
promise2
setTimeout1
promise3
setTimeout2
Copy the code
Event Loop in Node environment
The Event Loop in Node is implemented based on Libuv, which is a new cross-platform abstraction layer of Node. Libuv uses asynchronous and event-driven programming mode, and its core is to provide I/O Event Loop and asynchronous callback. Libuv’s API includes time, non-blocking networks, asynchronous file operations, child processes, and more.
Node Event Loop is divided into 6 stages:
- Timers: perform
setTimeout()
和setInterval()
Callback due in. - Pending callback: There are a few pending callbacks in the last loop
I/O
The callback is deferred until this stage of the round - Idle, prepare: Used internally only
- Poll: The most important stage, execution
I/O
The callback,Under the right conditionsWill block at this stage - Check: to perform
setImmediate
The callback - The close callbacks: execution
close
Callback to the event, for examplesocket.on('close'[,fn])
,http.server.on('close, fn)
None of the above six phases includes process.nexttick ()
Timers phase
The Timers phase performs setTimeout and setInterval callbacks and is controlled by the Poll phase.
The timers phase actually uses a minimum heap instead of a queue to hold all elements, since the timeout callback is called in the order of the timeout, rather than first-in-first-out queue logic. It’s not hard to understand why the Timer phase is on the first execution ladder. The timer is also inaccurate in Node, so that it can be as accurate as possible and its callback function can be executed as quickly as possible.
Pending callbacks phase
The Pending Callbacks stage is actually the CALLbacks stage of I/O. For example, some TCP error callbacks.
Poll phase
The poll phase has two main functions:
- perform
I/O
The callback - Handles events in a poll queue
When the Event Loop enters the poll phase and there are no tasks to execute in the Timers phase (that is, no timer callbacks), the following two situations will occur
- If the poll queue is not empty, the Event Loop executes them until they are empty or system-dependent.
- If the poll queue is empty, one of the following things happens
- If setImmediate() has a callback to perform, it immediately goes to the Check phase
- Check the tasks in the Timer phase. If so, the callback is returned to the Timer phase.
- If there is no setImmediate() to execute, the poll phase waits for the callback to be added to the queue before executing immediately, which is why we say the poll phase may block.
The check phase
After the poll phase, the setImmediate() callback is added to the check queue, which is a special counter that uses the Libuv API.
The Event Loop usually reaches the poll phase and waits for an incoming link or request, but setImmediate() is specified and the poll phase is idle. The poll phase is aborted and the execution of the check phase begins.
The close callbacks phase
If a socket or event handler suddenly closes/breaks (such as socket.destroy()), the close callback execution occurs in this phase.
setImmediate() vs setTimeout()
setImmediate
It is executed after the poll phase, the check phasesetTimeout
Execute when poll is idle and the set time reaches, in the timer phase
The order in which timers are executed will vary depending on the context in which they are invoked. If both are called from the main module, the timing is limited by process performance.
If it is outside the I/O cycle (that is, the main module), the order of execution of the two timers is uncertain because it is constrained by process performance:
// timeout_vs_immediate.js
setTimeout(() = > {
console.log('timeout');
}, 0);
setImmediate(() = > {
console.log('immediate');
});
Copy the code
$ node timeout_vs_immediate.js
timeout
immediate
$ node timeout_vs_immediate.js
immediate
timeout
Copy the code
If the two calls are moved within an I/O cycle, the immediate callback is always executed first:
// timeout_vs_immediate.js
const fs = require('fs');
fs.readFile(__filename, () = > {
setTimeout(() = > {
console.log('timeout');
}, 0);
setImmediate(() = > {
console.log('immediate');
});
});
Copy the code
$ node timeout_vs_immediate.js
immediate
timeout
$ node timeout_vs_immediate.js
immediate
timeout
Copy the code
The main advantage of using setImmediate () over setTimeout () is that if any timers are scheduled during the I/O cycle, setImmediate () will always execute ahead of any timers, regardless of how many timers exist.
nextTick queue
Process.nexttick () is not technically part of the Event Loop. Instead, regardless of the current phase of the current event loop, the nextTickQueue, if it exists, will be processed after the current operation completes, taking precedence over other Microtasks.
setTimeout(() = > {
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')})},0)
process.nextTick(() = > {
console.log('nextTick')
process.nextTick(() = > {
console.log('nextTick')
process.nextTick(() = > {
console.log('nextTick')
process.nextTick(() = > {
console.log('nextTick')})})})})// nextTick=>nextTick=>nextTick=>nextTick=>timer1=>promise1
Copy the code
The Event Loop of Node is different from that of the browser
In the browser environment, the microTask queue is executed after each MacroTask has been executed. In Node.js, microTasks are executed between phases of the event cycle, i.e. tasks in the MicroTask queue are executed after each phase is completed.
If you learn something new, or get a nice picture on the left, please give it a thumbs up
Reference article:
- This time, thoroughly understand the JavaScript execution mechanism
- Explore javaScript asynchrony and browser update rendering timing from the Event Loop specification
- Thoroughly understand JavaScript execution mechanics