17
头图

As we all know, JS is a single-threaded language, but browsers can handle asynchronous requests well, so why?

The execution environment of JS is generally browser and Node.js, the two are slightly different, here we only discuss the situation in the browser environment.

There are two kinds of tasks during the execution of JS, namely: synchronous tasks and asynchronous tasks.

  • Synchronous tasks: such as declaration statements, for, assignments, etc., are executed immediately from top to bottom and from left to right after reading.
  • Asynchronous tasks: such as ajax network requests, setTimeout timing functions, etc. are all asynchronous tasks. Asynchronous tasks will be coordinated through the event queue mechanism (first in, first out mechanism).

Event Queue

The tasks in the task queue are also divided into two types, namely: Macro-take and Micro-take

  • Macro tasks mainly include: scrip (JS overall code), setTimeout, setInterval, setImmediate, I/O, UI interaction
  • Micro tasks mainly include: Promise (focus on), process.nextTick (Node.js), MutaionObserver

Execution of the task queue is: to execute a macro task , during execution if the output of the new macro / micro tasks, it will push them into the corresponding task queue, in the implementation of a micro-task team after , after again Execute the macro task, and so on. repeated process above 1609e35142389f is called Event Loop .

Each cycle operation is called tick .

Understand the execution process of micro tasks and macro tasks

console.log("script start");

setTimeout(function () {
  console.log("setTimeout");
}, 0);

Promise.resolve()
  .then(function () {
    console.log("promise1");
  })
  .then(function () {
    console.log("promise2");
  });

console.log("script end");

According to the above content, analyze the execution steps:

  1. Macro task: execute the overall code (equivalent to the code in <script>

    1. Output: script start
    2. Encounter setTimeout, join the macro task queue, the current macro task queue (setTimeout)
    3. When you encounter a promise, join the micro task, the current micro task queue (promise1)
    4. Output: script end
  2. Micro task: Execute the micro task queue (promise1)

    1. Output: promise1 , a micro task is generated after then and added to the micro task queue, the current micro task queue (promise2)
    2. Execute then, output promise2
  3. Perform rendering operations and update the interface (knock on the blackboard to focus).
  4. Macro task: execute setTimeout

    1. Output: setTimeout

Promise execution

new Promise(..) is also a synchronous code and will be executed immediately. Only then the code executed asynchronously, which is a micro task.

console.log("script start");

setTimeout(function () {
  console.log("timeout1");
}, 10);

new Promise((resolve) => {
  console.log("promise1");
  resolve();
  setTimeout(() => console.log("timeout2"), 10);
}).then(function () {
  console.log("then1");
});

console.log("script end");

step analysis:

  • Current task queue: Micro task: [], Macro task: [ <script> ]
  1. Macro task:

    1. Output: script start
    2. Encounter timeout1, join the macro task
    3. When encountering Promise, output promise1 and directly resolve to add then to the micro task. When encountering timeout 2, add the macro task.
    4. Output script end
    5. The first execution of the macro task ends
  • Current task queue: micro task [then1], macro task [timeou1, timeout2]
  1. Micro task:

    1. Execute then1, output then1
    2. Micro task queue empty
  • Current task queue: micro task [], macro task [timeou1, timeout2]
  1. Macro task:

    1. Output timeout1
    2. Output timeout2
  • Current task queue: micro task [], macro task [timeou2]
  1. Micro task:

    1. Empty skip
  • Current task queue: micro task [], macro task [timeou2]
  1. Macro task:

    1. Output timeout2

async/await execution

Async and await are actually syntactic sugar for Generator and Promise.

The async function is no different from the normal function, it just means that there is an asynchronous operation method in this function and returns a Promise object

The translation is actually:

// async/await 写法
async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
// Promise 写法
async function async1() {
  console.log("async1 start");
  Promise.resolve(async2()).then(() => console.log("async1 end"));
}

Look at the example:

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log("async2");
}
async1();
setTimeout(() => {
  console.log("timeout");
}, 0);
new Promise(function (resolve) {
  console.log("promise1");
  resolve();
}).then(function () {
  console.log("promise2");
});
console.log("script end");

step analysis:

  • Current task queue: Macro task: [ <script> ], Micro task: []
  1. Macro task:

    1. output: async1 start
    2. When encountering async2, outputs: async2 , and then (async1 end) is added to the micro task
    3. Encounter setTimeout, join the macro task.
    4. Encounter Promise, output: promise1 , directly resolve, add then (promise2) to the micro task
    5. output: script end
  • Current task queue: micro task [promise2, async1 end], macro task [timeout]
  1. Micro task:

    1. output: promise2
    2. promise2 out of the team
    3. output: async1 end
    4. async1 end dequeue
    5. Micro task queue empty
  • Current task queue: micro task [], macro task [timeout]
  1. Macro task:

    1. output: timeout
    2. timeout goes out of the team, the macro task is cleared

"Task Queue" is an event queue (can also be understood as a message queue). When an IO device completes a task, an event is added to the "Task Queue" to indicate that the related asynchronous task can enter the "execution stack" . The main thread reads the "task queue", which is to read what events are in it.

In addition to the events of the IO device, the events in the "task queue" also include some user-generated events (such as mouse clicks, page scrolling, etc.). As long as the callback function is specified, these events will enter the "task queue" and wait for the main thread to read.

The so-called "callback" (callback) is the code that will be hung up by the main thread. Asynchronous tasks must specify a callback function. When the main thread starts to execute the asynchronous task, the corresponding callback function is executed.

"Task Queue" is a first-in-first-out data structure, and the first events are read by the main thread first. The reading process of the main thread is basically automatic. As long as the execution stack is emptied, the first event on the "task queue" will automatically enter the main thread. However, due to the "timer" function mentioned later, the main thread must first check the execution time. Certain events can only return to the main thread after the specified time.

---- Execute immediately when there is no code in JavaScript, it is executed as soon as possible when the process is idle

setTimerout is not accurate

From the above, we already know that setTimeout is a macro task, which will be added to the macro task queue and executed in order, if there is one before it.

setTimeout() is to tell JavaScript how long to add the current task to the queue.

If the queue is empty, then the added code will be executed immediately; if the queue is not empty, then it will execute after the previous code is executed.

Look at the code:

const s = new Date().getSeconds();
console.log("script start");
new Promise((resolve) => {
  console.log("promise");
  resolve();
}).then(() => {
  console.log("then1");
  while (true) {
    if (new Date().getSeconds() - s >= 4) {
      console.log("while");
      break;
    }
  }
});
setTimeout(() => {
  console.log("timeout");
}, 2000);
console.log("script end");

Because then is a micro task, it will be executed before setTimeout, so although setTimeout is a macro task added after two seconds, because the while operation in then was delayed by 4s, it was delayed until 4s before execution. setTimeout.

So the order of output is: script start, promise, script end, then1.
Output after four seconds: while, timeout

Note: What should be added about setTimeout is that even if the main thread is empty, 0 milliseconds is actually not reachable. According to HTML standards, the minimum is 4 milliseconds. Interested students can find out for themselves.

<!-- ### Asynchronous rendering strategy-->
<!-- Take Vue as an example nextTick -->

to sum up

There is a small tip: from the specification, microtask is executed prior to task, so if there is logic that needs to be executed first, it will be executed earlier than task in the microtask queue.

Finally, remember that JavaScript is a single-threaded language, and asynchronous operations are placed in the event loop queue, waiting for the main execution stack to execute, and there is no dedicated asynchronous execution thread.

reference

Original from the : 1609e3514245a0 link to

九旬
1.1k 声望1.2k 粉丝