Introduction#
It has been over two years since I entered the frontend field. In a previous article, I wrote about understanding the changes and constants in the frontend domain. Now, just like two years ago, various frameworks are emerging, making it overwhelming. Many times, I find myself asking, "Do I still need to learn this?" This easily leads to anxiety. Moreover, when faced with new things, I often feel lost. Ultimately, it is because my foundational knowledge is not solid enough. This is the purpose of writing this article - to improve my foundation and better understand the "changes and constants".
Let's get started#
Let's start the exploration with this piece of code:
console.log('Started')
// setTimeout1
setTimeout(function () {
console.log('timeout1')
// promise1
new Promise(function (resolve) {
console.log('timeout1_promise')
resolve()
}).then(function () {
console.log('timeout1_then')
})
}, 2000)
for (var i = 1; i <= 5; i++) {
// setTimeout2
setTimeout(function () {
console.log(i)
}, i * 1000)
console.log(i)
}
// promise2
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('then1')
})
// setTimeout3
setTimeout(function () {
console.log('timeout2')
// promise3
new Promise(function (resolve) {
console.log('timeout2_promise')
resolve()
}).then(function () {
console.log('timeout2_then')
})
}, 1000)
// promise4
new Promise(function (resolve) {
console.log('promise2')
resolve()
}).then(function () {
console.log('then2')
})
Before we begin, curious individuals may have two questions: why was JavaScript designed as a single-threaded language, and why is there a distinction between synchronous and asynchronous tasks. I won't go into detail about these two questions here. Interested students can refer to Mr. Ruan's article.
Now let's take a look at how JavaScript is roughly executed.
Which tasks are macro-tasks and which are micro-tasks? Here's a rough distinction:
Macro-tasks:
- script (the entire JavaScript code)
- setTimeout()
- setInterval()
- setImmediate()
- I/O
- UI render
Micro-tasks:
- promise
- async/await (same as above)
- process.nextTick
- MutationObserver
We have prepared the necessary knowledge, now let's start executing the code above.
- First, the script as a whole enters the main thread and encounters
console.log()
, immediately outputting "Started". - Next, it encounters
setTimeout
, and the callback functionfunction()
is dispatched to the macro-task Event Queue after 2 seconds (note that this is not the time of execution after 2 seconds), marked as setTimeout1. - When it encounters the
for
loop, it is executed directly, and similarly, the callback function insetTimeout
is dispatched to the macro-task Event Queue (this involves closure knowledge), marked as setTimeout2, and thenconsole.log
is executed, outputting "1, 2, 3, 4, 5". - When it encounters
promise
,new Promise
is executed directly, outputting "promise1".then
is dispatched to the micro-task Event Queue, marked as then1. - Next, it encounters another
setTimeout
, and the callback functionfunction()
is dispatched to the macro-task Event Queue after 1 second, marked as setTimeout3. - It encounters
promise
again, and similarly, outputs "promise2".then
is dispatched to the micro-task Event Queue, marked as then2.
The first round of event loop has been completed. The tasks in the Event Queue are shown in the following table:
Macro-task | Micro-task |
---|---|
setTimeout1 (2s later) | then1 |
setTimeout2 (1s later) | then2 |
setTimeout3 (1s later) |
According to the flowchart above, after the macro-task is executed, the monitoring process of the JavaScript engine will check if there are any micro-tasks that can be executed at this time. At this point, the then
dispatched to the micro-task Event Queue will be executed. It outputs "then1", "then2" in order.
The first round of event loop is complete.
Alright, now let's start the second round of event loop (1 second later).
- When it encounters
setTimeout2
, it outputs "6", and there are no micro-tasks to be executed. It then executes a new macro-task. - When it encounters
setTimeout3
, it outputs "timeout2",new promise
is immediately executed, outputting "timeout2_promise", andthen
is dispatched to the micro-task Event Queue. It is marked as then3.
The tasks in the Event Queue after the second round of event loop are as follows:
Macro-task | Micro-task |
---|---|
setTimeout1 (1s later) | then3 |
setTimeout2 (1s later) |
Similarly, after the macro-task is executed, the micro-task then3 is executed.
The second round of event loop is complete.
- When it encounters
setTimeout1
, it executesconsole.log
, outputting "timeout1".new promise
is immediately executed, outputting "timeout1_promise", andthen
is dispatched to the micro-task Event Queue. It is marked as then4. - After the third round of event loop, the micro-task
then
is executed, outputting "timeout1_then".
The third round of event loop is complete.
setTimeout2
will generate 4 macro-tasks one after another, outputting 6 every 1 second.
At this point, the entire code has finished executing.
Summary: After the macro-task is executed, the micro-tasks generated by that macro-task are executed. If new micro-tasks are generated during the execution of micro-tasks, the micro-tasks will continue to be executed. After the micro-tasks are executed, the next round of macro-tasks begins in the macro-task.