preface
As we all know, JavaScript is a single-threaded language. Although HTML5 puts forward the Web Worker standard, allowing JavaScript to create multiple threads, its child threads are completely controlled by the main thread. It can’t be used to manipulate the DOM, so it doesn’t change the nature of JavaScript as a single-threaded language
Why single thread?
As a scripting language of the browser, JavaScript is a bridge between the user and the browser. It is naturally unavoidable to manipulate the DOM and render the DOM, which is fundamental and important. With multiple threads, what if at some point one thread adds a property to a DOM element and another thread wants to remove that property? Which one should dominate? So its use has determined its single-threaded nature.
Task queue
Single thread, this smell of the task execution needs to be executed one by one, and how to ensure that the order of execution is predictable, this time will lead to the concept of the task queue, since it is called a queue, naturally follow the first-in-first-out (FIFO) principle, the First to enter the queue the First to execute.
The problem is that if some non-obstructive tasks take a long time to execute, such as (Ajax requests, etc.), and require time to wait for the execution to finish, the CPU at this point will cause unnecessary waste of resources, so in JavaScript, The tasks are categorized into synchronous tasks and asynchronous tasks
Synchronous task: it will enter the main thread of JS. The task executed on the main thread will continue to the next task only after the previous task is completed
Asynchronous tasks: Asynchronous tasks do not enter the main thread, but enter the Event Table, and enter the Task Queue after meeting the conditions. The execution of an asynchronous Task is to push an Event in the Task Queue after receiving the response result of the asynchronous Task execution, and wait for the completion of all synchronous tasks in the execution stack. It reads the “task queue” to see what events are in it, and the asynchronous tasks corresponding to these events finish waiting, enter the execution stack, and start executing
To sum up, asynchronous tasks work like this
All synchronous tasks are executed on the main thread, forming an execution stack. 2. Outside the main thread, a task queue is maintained. If the synchronization task on the execution stack is completed, the system reads the task queue and executes the tasks in sequence. 4. Repeat the above steps for the main threadCopy the code
Event loops and task queues
Task queue is actually an event queue, which can also be understood as a message queue. When the IO device completes a task, an event is added to the task queue, indicating that the relevant corresponding asynchronous tasks can enter the execution stack. After the main thread completes the synchronization task, it reads the task queue, essentially reading the events inside it to execute the synchronization task.
The events in the task queue, in addition to some IO device events, also include some events generated by user behaviors, such as click, double click, get focus and other callbacks. These callback functions (events) are pushed into the task queue, waiting for the main thread to read and then executed successively
The essence of the queue is a data structure of first in first out, that is to say, the implementation of the event will head shot out from the team, in the queue to empty, and the main thread execution stack, is in the reading task queue events, this process is a cycle of constantly, until the queue is empty, the current execution environment will not end, So this mechanism is also called an Event Loop
example
Now, let’s take a small example to illustrate
console.log(1)
console.log(2)
console.log(3)
setTimeout(function() {
console.log(0)},10)
// Output 1, 2, 3, 0
Copy the code
In the above example, the timer setTimeout is an asynchronous task. If the remaining three consoles are synchronous tasks, they will be executed according to the JavaScript mechanism, which is explained into the main thread execution stack, and executed step by step. SetTimeout will suspend the main thread. Until 10 milliseconds later, the asynchronous task ends, the corresponding callback function (event) is put into the event queue (task queue), which is read successively after the synchronization task is executed in the main thread.
The above example could be tweaked a bit
console.log(1)
console.log(2)
console.log(3)
setTimeout(function() {
console.log(0)},10)
console.log(5)
// output 1, 2, 3,5, 0
Copy the code
In this example, we have added a console after the timer, but the output sequence is still in front of the timer. This further shows that the main thread will read all synchronization tasks first and then execute them in sequence.
Macro and micro tasks
In my understanding, JS is divided into synchronous and asynchronous tasks, and asynchronous tasks are divided into two types, one is macro task, and the other is micro task. The execution order of synchronous task takes precedence over asynchronous task, while micro task takes precedence over macro task.
Macro task
# | The browser | Node |
---|---|---|
setTimeout | Square root | Square root |
setInterval | Square root | Square root |
setImmediate | x | Square root |
requestAnimationFrame | Square root | x |
Micro tasks
# | The browser | Node |
---|---|---|
process.nextTick | x | Square root |
MutationObserver | Square root | x |
Promise.then catch finally | Square root | x |
Now, let’s take a more detailed example
console.log(1)
setTimeout(function(){console.log(2)}, 10)
console.log(4)
function fn() {
return new Promise((r, j) = > {
console.log(6)
r()
})
}
Promise.resolve().then(function() {
console.log(5)
})
fn().then(function() {
console.log(7)})/ / output
// 1, 4, 6, 5, 7, 2,
Copy the code
When a macrotask is encountered, the main thread suspends and the macrotask queue enters the Event Table. When a macrotask is encountered, the main thread suspends and the macrotask queue enters the Event Table. Then, a promise is encountered and placed in the microTask queue. Then, we call the fn function, enter the fn execution environment, print 6, and then call resolve. Drop the task into the microtask queue and exit the FN execution environment. At this point, the synchronization task of the main thread is finished, and the microtask queue is scanned. The first output is 5, and the second output is 7. Then the microtask queue is emptied, and the macro task queue is scanned, and the output is 2.
Note that process.nextTick is always greater than promise.then. In Node, _tickCallback is called every time a task in the TaskQueue is completed, and this _tickCallback essentially does two things:
1. All tasks in nextTickQueue are executed (maximum length: 1e4, Node version: v6.9.1)
2. After the first step, execute _runMicrotasks to execute the part of microTask (promise.then registered callback).
So clearly process.nexttick > promise.then
However, there are some differences between THE JS and NodeJS event loops, so I will write an article about nodeJS event loops sometime