takeaway
JavaScript is a single-threaded language and only one block of code can be executed at a time. In addition, when JS is executed, it will not wait for the asynchronous code to return the result and execute down, but directly execute down. The callback of the asynchronous code will enter a task queue, and after the synchronous code is executed, the callback in the task queue will be pushed into the main thread for execution through the Event Loop mechanism.
In this chapter we will analyze the mechanism behind this: JS synchronous, asynchronous, macro task, micro task, event loop
Why is JS single threaded
Suppose js is multi-threaded, js can manipulate the DOM through the DOM API, and now there are two threads operating on a DOM node at the same time, one thread deleting the node, and the other thread modifying the node, so who should listen to, so JS is designed as a single thread execution mechanism. Although HTML5 puts forward the Web Worker standard, allowing JavaScript to create multiple child threads, the child threads are controlled by the main thread and cannot manipulate DOM, so it does not change the nature of JavaScript being executed in a single thread.
Task queue
Single threading means that tasks are queued until the previous one is finished. That’s fine if it’s computationally-heavy code, but waiting is not necessary for operations like Ajax, so JS tasks are divided into two categories: synchronous and asynchronous. A task queue is a first-in, first-out data structure in which the first event is read by the main thread.
1. Synchronize tasks
A synchronization task is a task that is queued to be executed on the main thread. A synchronization task is executed only when the previous task is completed.
2. Asynchronous tasks
An asynchronous task enters the task queue instead of the main thread. When the execution of the main thread is complete, it checks whether there are asynchronous tasks waiting to be executed in the task queue. If there are asynchronous tasks waiting to be executed, it ends the waiting state and pushes the task to the main thread for execution.
Event and callback functions
A task queue is an event queue (also known as a message queue). When an IO device completes a task (for example), it adds an event to the task queue. The main thread reads the task queue, reading what events are in it. In addition to the IO device events, there are also user interaction events, such as mouse clicks, moves in and out, etc., which are added to the task queue, waiting for the main thread to read whenever a callback function is defined. What is a callback function, is the code that the asynchronous task enters the main thread to execute, and the main thread executes the callback function when the asynchronous task executes.
Macro and micro tasks
Macro and micro tasks are tasks in a task queue and are asynchronous
1. The macro task
Macro tasks include:
- Overall code script
- Timers setTimeout and setInterval
- I/O INPUT and output
- The UI rendering
- postMessage
- MessageChannel
- requestAnimationFrame
- SetImmediate (Node. Js environment)
2. Micro tasks
Micro-tasks include the following
- new Promise.then()
- MutaionObserver
- Process. NextTick (Node. Js environment)
EventLoop
EventLoop is used to determine when tasks in the task queue will be executed. The main thread reads the task queue, and this is repeated over and over again. This mechanism is called an EventLoop. The specific rules are as follows:
- All code is executed as macro tasks into the main thread
- When a macro task is encountered and a callback function is defined, the callback function is added to the macro task queue. When a microtask is encountered and a callback function is defined, the callback function is added to the microtask queue
- When the macro task is finished, read the microtask queue, execute it as soon as possible, and complete the execution, empty the microtask queue
- After all microtasks are completed, proceed
The UI render to render
- When the macro task of this round is completed, proceed to the next macro task and return to step 2 until the macro task queue and micro task queue are cleared
For example,
console.log("1"); setTimeout(function () { console.log("2"); setTimeout(function () { console.log("3"); }, 0); new Promise(function (resolve) { console.log("4"); resolve(); }).then(function () { console.log("5"); }); }, 0); console.log("6"); new Promise(function (resolve) { console.log("7"); resolve(); }).then(function () { console.log("8"); }); setTimeout(function () { console.log("9"); setTimeout(function () { console.log("10"); }, 0); new Promise(function (resolve) { console.log("11"); resolve(); }).then(function () { console.log("12"); }); }, 0); // 1, 6, 7, 8, 2, 4, 5, 9, 11, 12, 3, 10Copy the code
Let’s break it down:
- First event loop
- console.log(‘1’); Sync code executes immediately, output 1
- SetTimeout1 pushes the callback function to the macro task list after 0 seconds
- console.log(“6”); Sync code executes immediately, output 6
- The executor in new Promise is synchronized code that executes console.log(“7”), prints 7, and then’s callback to enter the microtask queue, called then1
- SetTimeout2 pushes the callback function to the macro task list after 0 seconds
- The macro task completes, executes the microtask queue, then1, enters the main thread, executes console.log(“8”), and prints 8
- The macro task of this round is complete
- Second event loop
- Read setTimeout1 into the main thread execution, synchronization console.log(“2”), output 2
- SetTimeout3 pushes the callback function to the macro task list after 0 seconds
- The executor in new Promise is synchronized code that executes console.log(“4”), prints 4, and then’s callback to enter the microtask queue, called then2
- The macro task completes, executes the microtask queue, then2, enters the main thread, executes console.log(“5”), and prints 5
- The macro task of this round is complete
- Third event loop
- Read setTimeout2 into the main thread execution, synchronization console.log(“9”), output 9
- SetTimeout4 pushes the callback function to the macro task list after 0 seconds
- The executor in new Promise is synchronized code that executes console.log(“11”), prints 11, and then’s callback to enter the microtask queue, called then3
- The macro task completes, executes the microtask queue, then3, enters the main thread, executes console.log(“12”), and prints 12
- The macro task of this round is complete
- Fourth event loop
- Read setTimeout3 into the main thread execution, synchronization console.log(“3”), output 3
- The macro task of this round is complete
- Fifth event loop
- Read setTimeout4 into the main thread execution, synchronization console.log(“10”), output 10
- The macro task of this round is complete