不求谌解

不求谌解

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

Revisiting JavaScript Execution Mechanism

image

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.

Event Loop

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 function function() 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 in setTimeout is dispatched to the macro-task Event Queue (this involves closure knowledge), marked as setTimeout2, and then console.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 function function() 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-taskMicro-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", and then 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-taskMicro-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 executes console.log, outputting "timeout1". new promise is immediately executed, outputting "timeout1_promise", and then 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.

async#

node#

References#

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