不求谌解

不求谌解

💻 Web Dev / Creative 💗 ⚽ 🎧 🏓
twitter
github
jike
email

Re-exploring the JS execution mechanism

Introduction#

I have been in the frontend field for over two years now. Previously, I wrote in an article about understanding the changes and constants in the frontend domain. Just like two years ago, various frameworks are emerging one after another, dazzling us. Many times I ask myself, "Is this still worth learning?" This easily leads to feelings of anxiety. Moreover, when faced with new things, I often feel at a loss. Ultimately, it comes down to a lack of solid foundational knowledge and insufficient internal skills. This is also the purpose of writing this article—to enhance my internal skills and better understand "changes and constants."

Let's Get Started#

Let's begin our 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, those who like to ponder problems 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 elaborate on these two questions here. Interested readers can refer to Teacher Ruan's article.
Now let's take a look at how JS is generally executed.

Event Loop

Which are macro tasks and which are micro tasks? Generally, they can be distinguished as follows.

Macro Tasks (macro-task)

  • script (entire JavaScript code)
  • setTimeout()
  • setInterval()
  • setImmediate()
  • I/O
  • UI render

Micro Tasks (micro-task)

  • promise
  • async/await (same as above)
  • process.nextTick
  • MutationObserver

The preparatory knowledge is almost ready, now let's start executing the above code.

  • First, the entire script enters the main thread, encountering console.log(), which immediately outputs "Started."
  • Next, it encounters setTimeout, and after 2 seconds, the callback function function() is dispatched to the macro task Event Queue (note that this is not executed after 2 seconds), marked as setTimeout1.
  • When it encounters the for loop, it executes directly, similarly dispatching the callback function in setTimeout to the macro task Event Queue (this involves knowledge of closures), marked as setTimeout2, and then executes console.log, outputting "1, 2, 3, 4, 5."
  • When it encounters promise, new Promise executes immediately, outputting "promise1." The then is dispatched to the micro task Event Queue, marked as then1.
  • Next, it encounters another setTimeout, and after 1 second, the callback function function() is dispatched to the macro task Event Queue, marked as setTimeout3.
  • It encounters promise again, similarly outputting "promise2," and the then is dispatched to the micro task Event Queue, marked as then2.

The macro tasks for the first round of the event loop have been executed. The tasks in the Event Queue are as follows:

Macro TasksMicro Tasks
setTimeout1 (2s later)then1
setTimeout2 (1s later)then2
setTimeout3 (1s later)

According to the flowchart above, after the macro tasks are executed, the JS engine's monitoring process checks if there are any micro tasks that can be executed. At this point, the then dispatched to the micro task Event Queue will be executed, outputting "then1" and "then2" in sequence.

The first round of the event loop has been completely executed.

Now, let's start the second round of the event loop (after 1 second).

  • Encountering setTimeout2, it outputs "6," and there are no micro tasks to execute. It proceeds to execute new macro tasks.
  • Encountering setTimeout3, it outputs "timeout2," new promise executes immediately, outputting "timeout2_promise," and the then is dispatched to the micro task Event Queue, marked as then3. The macro tasks for the second round of the event loop are complete. The tasks in the Event Queue are as follows:
Macro TasksMicro Tasks
setTimeout1 (1s later)then3
setTimeout2 (1s later)

Similarly, after the macro tasks are executed, the micro task then3 for this round is executed.

The second round of the event loop has been completely executed.

  • Encountering setTimeout1, it executes console.log, outputting "timeout1," new promise executes immediately, outputting "timeout1_promise," and the then is dispatched to the micro task Event Queue, marked as then4.
  • The macro tasks for the third round of the event loop are complete, and the micro task then for this round is executed, outputting "timeout1_then."

The third round of the event loop has been completely executed.

  • In setTimeout2, four macro tasks will be generated in succession, each outputting a "6" every second.

Thus, the entire code execution has concluded.

Summary: After executing the macro tasks, execute the micro tasks generated by that macro task. If new micro tasks are generated during the execution of micro tasks, continue executing the micro tasks. Once the micro tasks are completed, return to the macro tasks to start the next round of the loop.

async#

node#

References#

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.