1. Why is JavaScript single-threaded?
One of the hallmarks of the JavaScript language is single-threaded, which means you can only do one thing at a time.
The single thread of JavaScript, relative to its purpose. As a browser scripting language, JavaScript’s primary purpose is to interact with users and manipulate the DOM. This means that it has to be single-threaded, which can cause complex synchronization problems. For example, if there are two threads of JavaScript at the same time, one thread adds content to a DOM node, and the other thread removes that node, which thread should the browser use?
So, to avoid complexity, JavaScript has been single-threaded since its inception, and this has been a core feature of the language and will not change.
2. Task queue
Single threading means that all tasks need to be queued until the first one is finished before the next one can be executed. If the first task takes a long time, the second task has to wait forever. The designers of the JavaScript language were aware of this problem,
All tasks can be categorized into two types, synchronous and asynchronous.
- A synchronization task refers to a task that is queued to be executed on the main thread. The next task can be executed only after the first task is completed.
- Asynchronous tasks are tasks that do not enter the main thread but enter the task queue. The task queue notifies the main thread that an asynchronous task is ready to execute.
According to the specification, the event loop is passed“Task queue“To coordinate. An Event Loop can have one or more task queues. A task queue is a collection of ordered tasks. Each task has a task source,“Tasks from the same task source must be placed in the same task queue, while tasks from different sources are added to different queues“. Apis such as setTimeout/Promise are task sources, and what enters the task queue is the specific execution task they specify.
3. Macrotask and Microtask of JS
Macrotask and MicroTask represent two classifications of asynchronous tasks.
3.1 the macro task
(macrotask), which can be understood as a macrotask each time the execution stack executes the code (including fetching an event callback from the event queue and placing it on the execution stack each time).
In order to ensure orderly execution of internal JS (MacroTask) and DOM tasks, browsers will re-render the page after one (MacroTask) execution and before the next (MacroTask) execution. The process is as follows:
❝
(macrotask) — – > > rendering (macrotask) – >…
❞
3.2 the task
A microtask (also known as a microtask) is a task that can be executed immediately after the execution of the current task. That is, after the current task, before the next task, and before rendering.
So it responds faster than setTimeout (setTimeout is task) because there is no need to wait for rendering. That is, after a macroTask has been executed, all microTasks created during its execution have been executed (before rendering).
When a task is suspended, the JS engine will divide all tasks into these two queues according to their categories. First, the JS engine will take out the first task from the MacroTask queue (also known as task queue), and then take out all tasks in the MicroTask queue to execute them in sequence. The MacroTask task is then fetched, and the cycle continues until both queues are complete. Gold digger chart record:Macro task and micro task execution process:
- The entire script script begins execution as a macro task
- When a microtask is encountered, it is pushed to the microtask queue, and a macro task is pushed to the macro task queue
- After the macro task is executed, check whether any microtask can be executed
- If any executable microtasks are found, all the microtasks are executed
- When the macro task completes, the render is checked, and the GUI thread takes over
- After rendering, the JS thread continues to take over, starting new macro tasks, and does so again and again until all tasks are completed
PostMessage MessageChannel setImmediate Mediate (Node.js) Promise. then catch finally process.nextTick(Node use) MutationObserver (browser use)
3.3 pay attention to the point
- Immediate execution in Promise and Async
We know that asynchrony in promises is embodied in then and catch, so the code written in promises is executed immediately as a synchronous task. In async/await, the code is executed immediately before an await occurs. So what happens when we get await?
- What does await do?
‘await’ is literally waiting. ‘await’ is waiting for an expression whose return value can be a Promise object or other value.
Many people think that await is waiting for a subsequent expression to complete before continuing to execute code. “Actually await is a signal to give up the thread”. The expression after await is executed once, the code after await is added to the microtask, and then the whole async function is jumped to execute the code after await. Because because async await itself is the syntactic sugar of the Promise +generator. So the code after await is microtask.
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
Copy the code
Is equivalent to
async function async1() {
console.log('async1 start');
Promise.resolve(async2()).then((a)= > {
console.log('async1 end');
})
} Copy the code
“an“:
console.log('a')
setTimeout(function(){
console.log('b')
}, 200)
setTimeout(function(){ console.log('c') }, 0) console.log('d') Copy the code
A B C D
Log (‘a’) and console.log(‘d’) exist in the main thread. Timer setTimeout(window.settimeout () method sets a timer that executes a function or a specified piece of code when the timer expires.) Delay execution for a period of time. As the name implies, the asynchronous task enters the event queue and waits for the completion of the main task before entering the main thread for execution.
The timer’s delay time of 0 is not immediately executed, but it simply means that the main thread is executed earlier than other timers. Therefore, c is printed before B. Question 2:
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i)
}, 1000)
}
Copy the code
Result: Ten 10s each time the for loop encounters setTimeout, it will be put into the event queue for execution until the end of the whole loop. I will be used as the global variable. When the loop ends, I = 10, the value of I will already be 10 and the result will be ten 10s.
Change var to let, variable scope is different, let is used in the current loop (” let declared variable, let is only valid in the code block “), so the timer enters the event queue with different I, the final print will be 0, 1, 2… 9. Question 3:
const p = new Promise(resolve= > {
console.log('a')
resolve()
console.log('b')
})
p.then((a)= > { console.log('c') }) console.log('d') Copy the code
A b C
The entire length of the script
Enter the macro task queue and start executing,- Promise creation executes immediately and prints
a b
. - encounter
promise.then
Enter the microtask queue, - encounter
console.log('d')
printd
. The whole piece of code
As the macro task is completed, there is an executable microtask, start the microtask, printc
。
“Question 4:“
setTimeout(function(){
console.log('setTimeout')
}, 0)
const p = new Promise(resolve= > {
console.log('a') resolve() console.log('b') }) p.then((a)= > { console.log('c') setTimeout(function(){ console.log('then the setTimeout') }, 0) }) console.log('d') Copy the code
Result: a B D C setTimeout then
setTimeout
Enter the macro task queue,promise
Create immediately execute, printa b
.- encounter
promise.then
Enter the microtask queue, - encounter
console.log('d')
printd
. - There are microtasks that can be performed, printing
c
Encountered,setTimeout
Push it into the macro task queue - If the timer delay is the same, macro tasks are executed in sequence and printed separately
setTimeout
Then the setTimeout
“Question 5:“
console.log('a');
new Promise(resolve= > {
console.log('b')
resolve()
}).then((a)= > { console.log('c') setTimeout((a)= > { console.log('d') }, 0) }) setTimeout((a)= > { console.log('e') new Promise(resolve= > { console.log('f') resolve() }).then((a)= > { console.log('g') }) }, 100) setTimeout((a)= > { console.log('h') new Promise(resolve= > { resolve() }).then((a)= > { console.log('i') }) console.log('j') }, 0) Copy the code
A B C H J I D E f g
- print
a
promise
Execute immediately, printb
promise.then
Push into the microtask queuesetTimeout
Push into the macro task queue- The whole code executes, starts the microtask, prints
c
Encountered,setTimeout
Push the macro task queue to queue for execution - When no microtask is available, the macro task is executed, and the timer is queued according to the delay time
- print
h j
,promise.then
Push into the microtask queue - There are microtasks that can be performed, printing
i
To continue with the macro task, printd
- Execute the macro task with a delay of 100, print
e f
To perform microtask printingg
, all tasks completed
“Topic five“
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() { console.log('async2') } console.log('script start') setTimeout(function () { console.log('setTimeout') }, 0) async1() new Promise(function (resolve) { console.log('promise1') resolve() }).then(function () { console.log('promise2') }) console.log('script end') Copy the code
Script start async1 start async2 promise1 Script end async1 end promise2 setTimeout
-
First, the event loop starts from the macroTask queue, where there is only one script task. When a task source is encountered, the task is first distributed to the corresponding task queue. So, the first step of the above example is as follows:
-
Then we see that first two async functions are defined, then we move on, and then we encounter
console
Statement, direct outputscript start
. After output, the script task continues to execute, encounteringsetTimeout
, as a macro task source, it will first distribute its tasks to the corresponding queue: -
The script task continues and executes the async1() function. As described earlier, the code in async before await is executed immediately, so async1 start is immediately printed.
When an await is encountered, the expression following an await is executed once, so the output is immediately followed
async2
“, and then await the code after “await” that isconsole.log('async1 end')
Join the Promise queue in microTask, and then jump out of async1 to execute the following code. -
The script task continues and encounters a Promise instance. Since the function in a Promise executes immediately, subsequent.then packets are distributed to the MicroTask Promise queue. Therefore, promise1 is printed and resolve is executed to allocate promisE2 to the corresponding queue.
-
The script task continues until script end is printed, and the global task is complete.
According to the above, after each macro task is executed, the presence of Microtasks is checked; If so, execute Microtasks until the Microtask Queue is cleared.
So after the script task is finished, the search starts to clear the microtask queue. In this case, the Promise queue has two tasks async1 end and promise2, so asynC1 end, promise2 is output in sequence. The first round of the loop is complete when all Microtasks have been executed.
-
At the start of the second loop, we jump back into async1 to execute the following code, and then hit async1 End with the synchronization task console statement. This completes the second cycle. (it can also be interpreted as being added to a script task queue, so it will be executed with setTimeout queue first.)
-
The second cycle starts again with the macro task queue. At this point, there is only one setTimeout in the macro task. You can take it out and output it directly. At this point, the whole process ends.
A variant:
❝
Make async2 a Promise function as well, with the following code:
❞
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() { //async2 makes the following changes: new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); } console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0) async1(); new Promise(function(resolve) { console.log('promise3'); resolve(); }).then(function() { console.log('promise4'); }); console.log('script end'); Copy the code
Result: script start async1 start promise1 promise3 script end promise2 async1 end promise4 setTimeout
After the first MacroTask execution, after script end is printed, all microTasks are cleaned up. So promisE2, Async1 end, promise4, and not much else.
Variable type 2:
❝
Change the code after “await” in async1 and the code after “await” in async2 to asynchronous as follows:
❞
async function async1() {
console.log('async1 start')
await async2()
// Change as follows:
setTimeout(function () {
console.log('setTimeout1') }, 0) } async function async2() { // Change as follows: setTimeout(function () { console.log('setTimeout2') }, 0) } console.log('script start') setTimeout(function () { console.log('setTimeout3') }, 0) async1() new Promise(function (resolve) { console.log('promise1') resolve() }).then(function () { console.log('promise2') }) console.log('script end') Copy the code
Result: script start async1 start promise1 script end promise2 setTimeout3 setTimeout2 setTimeout1
After the output is promise2, setTimeout is added to the queue in the sequence of 3, 2, 1.
Change in three:
❝
Face by the original problem, the whole is much the same, the code is as follows:
❞
async function a1() {
console.log('a1 start')
await a2()
console.log('a1 end')
}
async function a2() { console.log('a2') } console.log('script start') setTimeout((a)= > { console.log('setTimeout') }, 0) Promise.resolve().then((a)= > { console.log('promise1') }) a1() let promise2 = new Promise((resolve) = > { resolve('promise2.then') console.log('promise2') }) promise2.then((res) = > { console.log(res) Promise.resolve().then((a)= > { console.log('promise3') }) }) console.log('script end') Copy the code
Script start A1 start A2 promise2 script end promise1 A1 end promise2. Then promise3 setTimeout
4. To summarize
JavaScript
Single-threaded tasks need to be queued for execution- Synchronous tasks queue up in the main thread, and asynchronous tasks queue up in the event queue to be pushed to the main thread
- The timer delay is
0
It does not execute immediately, but simply means that the timer is executed earlier than other timers - To further understand JS execution mechanism by macro task and micro task
- The whole code is executed as a macro task, and the macro task and micro task enter the corresponding queue during execution
- The whole code is executed to see if there are any tasks waiting to be executed in the microtask queue. If so, all the microtasks are executed until the tasks in the microtask queue are completed. If not, the new macro task is continued to be executed
- Execute a new macro task and push any microtask encountered during the execution of the macro task to the microtask queue
- Repeat until all tasks are completed
More on the Event Loop JS macro and micro tasks
If you find this article useful, please give me a thumbs up at 🍀
This article is formatted using MDNICE