Writing in the front
- What is the event loop?
- What is the function of the event loop?
- How does the event loop loop?
The event loop is also often encountered in interviews and written tests. It may not be directly asked what the time loop is, but I believe that most people have encountered the question to write the output sequence.
If you’re like me, you have questions about the above three. So, you can read on
First, we all know that JavaScript is a single-threaded language. This is because the main function of JavaScript is to interact with the user, such as rendering animation, loading data, etc. If JavaScript is running in multiple threads, there may be problems such as inconsistent DOM loading.
However, if it is completely single-threaded, the next task can be executed only after the previous task is completed, which is very inefficient. Hence the concept of an event loop.
Event loops are responsible for executing code, collecting and processing events, and executing subtasks in queues. (From MDN)
So what is the logic of the loop of events?
To understand event loops, it’s important to understand the concepts that JS code contains when it executes: stacks, heaps, and queues.
Run-time concepts
- The stack
The function call forms a stack of several frames.
When the function is called, a frame containing arguments and local variables is pushed into the stack to generate the first frame. If another function is called inside the function, the same logic is pushed to generate frame 2. Thus the stack is a LIFO (First In Last Out) structure, so incoming frames are executed and ejected until the stack is empty.
- The heap
Block storage space, where variables of object types are stored in the heap.
- The queue
Holds messages to be processed by the JS runtime, and each message is associated with a callback function that processes it.
The queue is a FIFO (First In First Out) structure, so the runtime retrives messages In the order they are entered, invokes the associated function as an argument, and generates a new frame, which is placed on the execution stack. The next cycle of message processing occurs until the stack is empty.
Synchronous and asynchronous tasks
We also know that JS code to perform tasks can be divided into synchronous tasks and asynchronous tasks.
To put it simply, synchronous tasks are plain JS code, while asynchronous tasks are Ajax requests, setTimeout, setInterval and other tasks that need to wait to get results.
Perform logical
Synchronous and asynchronous tasks are handled differently:
- The code goes to the execution stack.
- Determine whether the code obtained is synchronous task or asynchronous task. If it is synchronous task, go to Step 3. If the task is asynchronous, go to Step 4.
- The synchronization task is directly executed in the main thread.
- Asynchronous tasks are registered in the Event Table. When the specified Event is completed, the Event Table moves the callback function to the Event Queue.
- Does the JS engine determine whether the main thread task is complete? If yes, go to Step 6. If no, go to Step 3.
- Check whether the Event Queue is empty. If not, the function in the Queue is read and executed in the main thread.
The above process is repeated, forming a JS event loop.
Take a look at a simple code example:
setTimeout((a)= > {
console.log('I'm an asynchronous task');
},0)
(function (){
console.log('I'm a sync task'); }) ()// I am a synchronization task
I am an asynchronous task
Copy the code
The execution flow of the above code is as follows:
- SetTimeout is an asynchronous task that goes into the Event Table and registers the callback function, which is the arrow function in the code.
- The immediate execution function is a synchronization task. Enter the main thread to execute the task directly and output “I am a synchronization task”.
- The setTimeout precondition completes, and the callback enters the Event Queue.
- The JS engine detects that the main thread task has finished executing, so it reads the setTimeout callback from the Event Queue and prints “I am an asynchronous task”.
As you can also see from the above code, even if the setTimeout delay event is 0, it is executed after the synchronization task.
The synchronization task is simple and easy to understand, so I won’t go into details. The following focuses on the types of asynchronous tasks and introduces the concepts of macro-task and micro-task.
Common asynchronous tasks mainly include setTimeout, setInterval, and Promise.
setTimeout(callback, time)
We only use setTimeout to delay the time after which the callback is executed. But is it really on time after a latency time period? Let’s do an experiment:
console.log(new Date())
setTimeout((a)= > {
console.log(new Date())
console.log('execute after 3s')},3000)
for(let i=0; i < 40000; i++){
console.log(i-i)
}
// Sun Jun 14 2020 13:51:50 GMT+0800
/ / 40000 0
// Sun Jun 14 2020 13:51:58 GMT+0800
// Execute after 3s
Copy the code
The output above is for reference only, and the interval may vary from computer to computer. However, it can be seen that by the time the setTimeout callback is executed, more than 3s have passed. The more times the for loop is executed, the more obvious the effect will be.
Why is that? As mentioned above, asynchronous tasks will enter the Event Table first, and the setTimeout callback will enter the Event Queue after the current condition is completed, that is, 3s. However, the point is that my for loop has not finished executing at this point. The main thread is not empty, so there is no way for the Event Queue callback to be executed on the main thread.
So, setTimeout’s delay time is not accurate, and it depends on when the main thread is finished executing its callback, and then it goes to the Event Queue to read the task, and then it goes to the main thread to execute.
setInterval(callback, time)
Similar to setTimeout, the callback function is executed after a specified delay. The difference is that setInterval is executed in a loop, that is, it puts the callback function into the Event Queue once every specified time and waits for it to enter the main thread.
Promise
ES6 provides an asynchronous programming solution. For details, please refer to ruan Yifeng’s introduction to ECMAScript 6 or MDN documents
The argument functions accepted by a promise are executed as synchronous code, while the callback functions after a promise is resolved () or reject() are asynchronous tasks.
As mentioned earlier, JS will perform synchronous tasks first and then asynchronous tasks. Synchronous tasks are executed in code order, of course, but are asynchronous tasks also executed in code order? Let’s do an experiment:
setTimeout((a)= > {
console.log('I am a setTimeout')},0);
new Promise(function(resolve, reject){
console.log('Promise preconditions');
resolve('promise success');
}).then(res= > {
console.log(res)
})
// Promise preconditions
/ / promise success
/ / I'm setTimeout
Copy the code
It is clear from the above example that asynchronous tasks are not executed in code order. Here we have to mention the concept of macro task and micro task. In JS, a task is divided into synchronous and asynchronous tasks, but also divided into macro task and micro task. Because the execution logic of macro task and micro task is different, the callback function enters different task queues, so there are two kinds of task queues: macro task Event Queue and micro task Event Queue.
Macro and micro tasks
Macro task
Macro tasks mainly include: setTimeout, setInterval, ordinary JS code, I/O operations;
Micro tasks
Microtasks mainly include: Promise, process.nexttick ()
However, the common JS code in macro task is synchronous code, which has been explicitly executed directly into the main thread. Therefore, when discussing the execution order of asynchronous task, we ignore it and only care about setTImeout and setInterval, which are also the most common.
Perform logical
After the concept of macro task and micro task is added, the overall execution logic of JS code becomes:
- The resulting JS code goes into the main thread
- Determine the code encountered: If it is synchronous code, go to Step 3; If it is asynchronous code, go to Step 4.
- Synchronized code is executed directly into the main thread.
- Asynchronous code is classified: if it is a Promise, the new promise and accepted function parameters are executed immediately, and then and catch callbacks are placed in the Event Queue of microtasks. If it is setTimeout or setInterval, the callback function is registered in the Event Queue of the macro task.
- The JS engine determines whether the main thread is empty. If so, it reads all messages in the microtask Event Queue and executes them in sequence. Go to Step 6.
- When both the main thread and the microtask Event Queue are empty, the first message from the read macro task Event Queue is executed in the main thread. Go to Step 5.
The above process is repeated until the code is finished executing and the event loop is complete.
The order of execution can be simply summarized as: (Nuggets MD editor does not support flowcharts)
The full text core
Synchronize code –> All microtasks –> Single macro task –> All microtasks –> Single macro task….. (Repeat)
(The most important line of this article)
A more complicated example
Let’s look at an example:
setTimeout(function () {
console.log(" set1");
new Promise(function (resolve) {
resolve();
}).then(function () {
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then4");
});
console.log("then2 ");
});
});
new Promise(function (resolve) {
console.log("pr1");
resolve();
}).then(function () {
console.log("then1");
});
setTimeout(function () {
console.log("set2");
});
console.log(2);
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then3");
});
Copy the code
Analyze according to the above execution process:
- We will use setTimeout_1 instead of setTimeout_1. SetTimeout_1 has no latency, so put the callback function setTimeout_1_function into the macro task Event Queue.
Task classification | The message |
---|---|
Macro task Event Queue | setTimeout_1_function |
Microtask Event Queue |
- If a new Promise() is encountered, the parameter function will execute immediately, output “PR1”, resolve() will be called, the Promise will become a pity state, and then the callback function promise_1_THEN will enter the microtask.
Task classification | The message |
---|---|
Macro task Event Queue | setTimeout_1_function |
Microtask Event Queue | promise_1_then |
Output: pr1Copy the code
- SetTimeout_2_function is called by the macro task queue.
Task classification | The message |
---|---|
Macro task Event Queue | SetTimeout_1_function, setTimeout_2_function |
Microtask Event Queue | promise_1_then |
Output: pr1Copy the code
- In the console. The log (2); Synchronize code, execute immediately, output 2.
Task classification | The message |
---|---|
Macro task Event Queue | SetTimeout_1_function, setTimeout_2_function |
Microtask Event Queue | promise_1_then |
Output: PR1, 2Copy the code
- When a promise is encountered, the argument function executes immediately, with only resolve() and then callback promise_2_THEN entering the microtask queue.
Task classification | The message |
---|---|
Macro task Event Queue | SetTimeout_1_function, setTimeout_2_function |
Microtask Event Queue | Promise_1_then, promise_2_then |
Output: PR1, 2Copy the code
- This is the end of the first event cycle and the start of the second cycle. Perform all microtasks by first fetching the promise_1_THEN callback and printing “then1”.
For convenience, post the code for the callback function again
function () {
console.log("then1");
}
Copy the code
Task classification | The message |
---|---|
Macro task Event Queue | SetTimeout_1_function, setTimeout_2_function |
Microtask Event Queue | promise_2_then |
Output: PR1, 2,then1
Copy the code
- The microtask queue also has promise_2_THEN, which takes out execution and outputs “then3”.
function () {
console.log("then3");
}
Copy the code
Task classification | The message |
---|---|
Macro task Event Queue | SetTimeout_1_function, setTimeout_2_function |
Microtask Event Queue |
Output: PR1, 2,then1.then3
Copy the code
- The microtask queue is empty and a single macro task is executed. Take out setTImeout_1_function execution.
Console. log(“set1”) is the synchronization code, which is executed directly and outputs “set1”.
When a new Promise() is encountered, resolve() is immediately executed, and the then callback function promise_3_THEN is put into the microtask queue.
A single macro task is completed
function () {
console.log(" set1");
new Promise(function (resolve) {
resolve();
}).then(function () {
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then4");
});
console.log("then2 ");
});
}
Copy the code
Task classification | The message |
---|---|
Macro task Event Queue | setTimeout_2_function |
Microtask Event Queue | promise_3_then |
Output: PR1, 2,then1.then3,set1
Copy the code
- Promise_3_then to execute all microtasks.
If a new Promise() is encountered, resolve() executes immediately, and the then callback function promise_4_THEN enters the microtask queue.
When console.log(“then2”) is encountered, execute immediately and print “then2” directly.
function () {
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then4");
});
console.log("then2 ");
}
Copy the code
Task classification | The message |
---|---|
Macro task Event Queue | setTimeout_2_function |
Microtask Event Queue | promise_4_then |
Output: PR1, 2,then1.then3,set1.then2
Copy the code
- After the execution of the previous step, the microtask queue is still not empty, so the promisE_4_THEN is used for execution.
Directly print “then4”.
function () {
console.log("then4");
}
Copy the code
Task classification | The message |
---|---|
Macro task Event Queue | setTimeout_2_function |
Microtask Event Queue |
Output: PR1, 2,then1.then3,set1.then2,then4
Copy the code
- The microtask queue is empty and a single macro task is executed. setTimeout_2_function
I’m going to print “set2”
function () {
console.log("set2");
}
Copy the code
Task classification | The message |
---|---|
Macro task Event Queue | |
Microtask Event Queue |
Output: PR1, 2,then1.then3,set1.then2,then4,set2
Copy the code
- After the previous execution, the microtask queue and macro task queue are empty, and the event cycle is completed. So the final output looks like this:
Output: PR1, 2,then1.then3,set1.then2,then4,set2
Copy the code
So much for the loop of events. In fact, we just need to distinguish which are macro tasks and which are micro tasks, and then remember the loop logic of macro tasks and micro tasks, this kind of output sequence problem can be basically solved.
This article is written in a way that I think is easier to understand while I understand the mechanism of JS event loop. There may be some places that I don’t understand well or even wrong. Welcome to find and correct the problems, and we will make progress together
In this thank big guy @ssssyoki article “this time, thoroughly understand JavaScript execution mechanism” help, write really very detailed, small white artifact!!
Main reference articles:
- “This time, Understand JavaScript execution mechanics once and for all”
- Concurrency model and event loop