This article has participated in the activity of “New person creation Ceremony”, and started the road of digging gold creation together.

As we all know, JavaScript is a single-threaded language. Although web-worker is proposed in HTML5, it does not change the core that JavaScript is single-threaded. See this paragraph in the HTML specification:

To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. There are two kinds of event loops: those for browsing contexts, and those for workers.

To coordinate the actions of events, user interactions, scripting, UI rendering, and network processing, the user engine must use Event Loops. Event Loop contains two types: one is based on the Browsing Context and the other is based on Worker, both of which run independently. The following article uses an example to explain the event loop mechanism based on the Browsing Context. Here is a snippet of js code:

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');
Copy the code

Take a guess at the output order of this code, and then go to the browser console and type it in. See if the output order is the same as your guess. If it is, then you know something about JavaScript event loops. If the actual output is not in the same order as your guess, the rest of this article will help you out.

Task queue

All tasks can be divided into synchronous tasks and asynchronous tasks. Synchronous tasks, as the name implies, are immediately executed tasks. Synchronous tasks generally enter the main thread for execution, while asynchronous tasks, such as Ajax network requests and serTimeout timing function, are asynchronous tasks. Asynchronous tasks are coordinated through the mechanism of task Event Queue, which can be roughly illustrated by the following figure:

Synchronous and asynchronous tasks enter different execution environments. Synchronous tasks enter the main thread (main execution stack) and asynchronous tasks enter the Event Queue. If the main thread is empty, the Event Queue will read the corresponding task and push the main thread to execute it. The repetition of the above process is called the Event Loop. In the event cycle, each cycle operation is called TICK. Reading the specification, it can be seen that the task processing model of each tick is relatively complex, and the key steps can be summarized as follows: 1. Select the oldest task from this tick and execute it if there is one. 2. Check whether Microtasks exist. If Microtasks exist, execute them continuously until the Microtask Queue is cleared. Repeat the above steps for the main thread to illustrate the flow with a diagram:

One of the questions here is, what are microtasks? According to the specification, tasks are divided into two categories: Macro task and Micro task. After each Macro task, all Micro tasks should be cleaned up. Tasks mentioned in the following articles are referred to as macro tasks. The MCRO Task mainly includes: Script (overall code), setTimeout, setInterval, I/O, UI interaction events, setImmediate (Node.js environment)

Micro Task mainly includes: Promise, MutationObserver, Process.nexttick (Node.js environment) setTimeout/Promise and other apis are task sources, and the specific execution tasks specified by them enter the task queue. Tasks from different task sources enter different task queues. Where setTimeout and setInterval are homologous.

Analyzing sample code

Thousands of words, as with the example of the future is clear, we can follow the specification, step by step under the execution of the analysis of this example, first post the example code

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');
Copy the code
  1. The whole script enters the main thread as the first macro task, encounters console.log, and prints script start
  2. When a setTimeout is encountered, its callback function is dispatched to the macro task Event Queue
  3. When a Promise is encountered, its then function is assigned to the micro-task Event Queue, named THEN1, and then it is encountered and assigned to the micro-task Event Queue, named then2
  4. When console.log is encountered, print script end

So far, there are three tasks in the Event Queue, as shown in the following table:

Then execute then1, print promise1, then execute then2, print promise, and that clears up. So, execute setTimeout, print setTimeout. At this point, The output sequence is: script start, script end, promise1, promise2, setTimeout.

See if you got it. One more
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');
Copy the code

This is a little bit more complicated, so let’s look at it again:

First, the event loop starts from the macroTask queue. Initially, there is only one Scrip t(whole code) task in the macroTask queue. When a task source is encountered, the task is first distributed to the corresponding task queue. So, similar to the above example, console.log is encountered and script start is printed; Next, it encounters the setTimeout task source and dispatts it to the task queue, denoting timeout1. Then it encounters a promise, the code in the new Promise executes immediately, prints promise1, executes resolve, encounters setTimeout, dispatches it to the task queue, Call it timemout2, then distribute it to the microtask queue, call it then1; Then the macro task queue is checked. If the macro task queue is empty, the macro task queue is checked. Then the macro task queue is checked. Execute timeout1 and print timeout1. Then execute timeout2, which prints timeout2. At this point, all queues are cleared, and execution is complete. The output sequence is: script start, promise1, script end, then1, timeout1, timeout2. Use a flow chart for clarity:

conclusion

Here’s a tip: MicroTask execution takes precedence over Task execution by specification, so if there is logic that needs to be executed first, putting it into the MicroTask queue will be executed before the task. Finally, remember that JavaScript is a single-threaded language, and asynchronous operations are placed in an event loop queue waiting for the main execution stack. There is no dedicated asynchronous execution thread.