Introduction: Why is there an event loop
Important: javascript has been a single-threaded, non-blocking scripting language since its inception
Let’s talk about these two features of JavaScript:
- Single thread:
JavaScript is single-threaded. Single-threaded means that the JavaScript engine has only one thread (the main thread) that parses and executes JavaScript code and can only do one thing at a time. Single-threaded existence is inevitable. In a browser, if javascript is multi-threaded, it is contradictory when two threads simultaneously perform an operation on the DOM, such as adding an event to it while the other removes the DOM
- Non-blocking:
When our Javascript code runs an asynchronous task (Ajax, etc.), the main thread suspends the task and executes the callback function based on the result when the asynchronous task returns
How do you make it non-blocking? That’s where we need our main character, the Event Loop.
Event loops in the browser
Let’s look at a classic diagram that basically encapsulates the Event Loop. (From the talk — Philip Roberts: What exactly is an Event Loop? | behind Europe JSConf 2014) demonstrate written in Loupe is also the speaker ((Loupe is a visualization tool, can help you understand the JavaScript call stack/event loop/callback queue how to interact with each other))
When javascript code executes, different variables are stored in different locations in memory: in the heap and in the stack. There are objects in the heap. The stack holds basic type variables and Pointers to objects
Call Stack: When we call a method, js generates a context that corresponds to the method. The execution environment contains the method’s private scope, the reference to the upper scope, the method’s parameters, the variables defined in that scope, and the this object in that scope. When a series of methods are called in turn, since JS is single-threaded and can only execute one method at a time, the methods are queued in a separate place. This place is called the execution stack
For example, here is an execution of synchronous code
function a() {
b();
console.log('a');
}
function b() {
console.log('b')
}
a();
Copy the code
Let’s demonstrate the execution process of the code through Loupe:
- Execute the function a() to push first
- In a(), function b() is executed first and function b() is pushed
- Execute function b(), console.log(‘b’) to push
- Output b, console.log(‘b’) off the stack
- Function b() completes its execution, leaving the stack
- Console. log(‘a’) pushes, executes, outputs a, and exits the stack
- Function A completes, and the stack is off
The execution of synchronous code is relatively simple, but what about asynchronous execution?
Callback Queue: Instead of waiting for an asynchronous event to return, the JS engine suspends the event and continues to execute other tasks in the stack. When an asynchronous event returns a result, JS will add the event to a different queue from the current execution stack, which is called the event queue
Is put into the event queue is not immediately implement the callback, but wait for the current execution stack of all tasks are completed, the main thread idle state, the main thread to find whether there is a task in the event queue, if you have, then remove the top of the list events, and put the corresponding callback event in execution stack, and then perform the synchronization code
Here’s an example from Loupe officials:
$.on('button'.'click'.function onClick() {
setTimeout(function timer() {
console.log('You clicked the button! ');
}, 2000);
});
console.log("Hi!");
setTimeout(function timeout() {
console.log("Click the button!");
}, 5000);
console.log("Welcome to loupe.");
Copy the code
Let’s analyze the execution process:
- First of all, you register a click event, execute it asynchronously, and then you put it in
Web Api
中 - console.log(“Hi!” ), directly execute, output Hi
- perform
setTimeout
, asynchronously, to mount it - Run console.log(“Welcome to loupe.”) to output Welcome to loupe.
- Five seconds later,
setTimeout
Execute the callback, put the callback into the event queue, and once the main thread is idle, fetch and run - I clicked the button (I only did it once here), triggered the click event, put the callback to the click event into the event queue, and once the main thread is idle, take it out and run
- Run in the click event callback
setTimeout
- After 2 seconds,
setTimeout
Execute the callback, put the callback into the event queue, and once the main thread is idle, fetch and run
When you look back at this picture, it should give you a sense of clarity
The above process works like this. Queue.waitformessage () waits synchronously for messages to arrive (if no messages are currently waiting to be processed), so we call it an Event Loop.
while (queue.waitForMessage()) {
queue.processNextMessage();
}
Copy the code
Microtasks and macro tasks
Micro – Micro – a Task
Common micro-tasks: new Promise().then(callback), MutationObserve, and so on (async and await) are actually syntactic candies for promises
Macro – Macro – a Task
Common macro-Tasks: setTimeout, setInterval, script (overall code), I/O operations, UI interaction events, postMessage, etc
The execution order of the event loop
The result of the asynchronous task is put into an event queue. Depending on the type of asynchronous event mentioned above, the event is actually put into the corresponding macro and microtask queues
The process of Eveent Loop is as follows:
- Execute a macro task (usually starts with the overall code (
script
), if no optional macro task is available, the microtask is processed directly - If a microtask is encountered during execution, it is added to the task queue of the microtask
- If a macro task is encountered during execution, it is added to the task queue of the macro task
- After executing a macro task, it is necessary to check whether there are any tasks in the microtask queue that need to be executed. If there are, all tasks will be executed; if there are no tasks, go to the next step
- Check the render and then
GUI
The thread takes over the rendering and renders the browser - After rendering, the JS thread continues to take over, starting the next macro task… (Loop through the steps above)
As shown in the figure below:
Summary of execution sequence: execute the macro task, and then execute the microtask generated by the macro task. If a new microtask is generated during the execution of the microtask, the microtask will continue to execute. After the execution of the microtask is completed, it will return to the macro task for the next round of cycle
To understand this better, let’s look at an example
console.log('start')
setTimeout(function() {
console.log('setTimeout')},0)
Promise.resolve().then(function() {
console.log('promise1')
}).then(function() {
console.log('promise2')})console.log('end')
Copy the code
Let’s break it down:
- The global execution
script
Output the start, - perform
setTimeout
Push themacrotask
The queue,promise.then
Back into themicrotask
Queue, and finally executeconsole.log('end')
And the outputend
- global
script
It’s a macro task, it’s done and then it’s donemicrotask
Queue task, executepromise
The callback to printpromise1
promise
The callback function returns by defaultundefined
.promise
State changes tofullfill
Trigger the followingthen
Call back and continue pressing inmicrotask
The queue,event loop
Will put the currentThe microTask queue continues to execute until the second task is executed
Promise. then ‘prints promise2- At this moment
microtask
The queue is empty, and then the main thread is going to do somethingUI
Render work (not necessarily done), then start the next roundevent loop
, the implementation ofsetTimeout
Callback to print outsetTimeout
Therefore, the final results are as follows:
start
end
promise1
promise2
setTimeout
Copy the code
exercises
Increase the environment is that the written test will be out of the event loop interview, now actually may be more difficult than the example above, the reason is that the task and macro task involves a lot of knowledge points, this needs us to further consolidate the basis of our knowledge, I believe that can seriously the following topic, to be able to better grasp the event loop
I will not do the analysis, you can not understand the question in the comments section together to communicate
Subject to a
console.log('start');
setTimeout(() = > {
console.log('children2');
Promise.resolve().then(() = > {
console.log('children3'); })},0);
new Promise(function(resolve, reject) {
console.log('children4');
setTimeout(function() {
console.log('children5');
resolve('children6')},0)
}).then((res) = > {
console.log('children7');
setTimeout(() = > {
console.log(res);
}, 0)})Copy the code
Click to see the answer
start children4 children2 children3 children5 children7
Topic 2
const p = function() {
return new Promise((resolve, reject) = > {
const p1 = new Promise((resolve, reject) = > {
setTimeout(() = > {
resolve(1)},0)
resolve(2)
})
p1.then((res) = > {
console.log(res);
})
console.log(3);
resolve(4);
})
}
p().then((res) = > {
console.log(res);
})
console.log('end');
Copy the code
Click to see the answer
3 end 2 4
Topic 3
async function async1(){
console.log('async1 start')
await async2()
console.log('async1 end')}async function async2(){
console.log('async2')}console.log('script start')
setTimeout(function(){
console.log('setTimeout')},0)
async1();
new Promise(function(resolve){
console.log('promise1')
resolve();
}).then(function(){
console.log('promise2')})console.log('script end')
Copy the code
Click to see the answer
script start async1 start async2 promise1 script end async1 end promise2 setTimeout
Topic 4
let resolvePromise = new Promise(resolve= > {
let resolvedPromise = Promise.resolve()
resolve(resolvedPromise);
// Resolve (resolvedPromise)
// Promise.resolve().then(() => resolvedPromise.then(resolve));
})
resolvePromise.then(() = > {
console.log('resolvePromise resolved')})let resolvedPromiseThen = Promise.resolve().then(res= > {
console.log('promise1')
})
resolvedPromiseThen
.then(() = > {
console.log('promise2')
})
.then(() = > {
console.log('promise3')})Copy the code
Click to see the answer
promise1 -> promise2 -> resolvePromise resolved -> promise3
Topic 5
console.log('script start');
setTimeout(() = > {
console.log('Gopal');
}, 1 * 2000);
Promise.resolve()
.then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
async function foo() {
await bar()
console.log('async1 end')
}
foo()
async function errorFunc () {
try {
/ / Tips: implicit reference: https://zh.javascript.info/promise-error-handling: a try... catch
// The promise.reject () method returns a Promise object with a reason for rejecting it
// Promise.reject('error!!! ') === new Error('error!!! ')
await Promise.reject('error!!! ')}catch(e) {
console.log(e)
}
console.log('async1');
return Promise.resolve('async1 success')
}
errorFunc().then(res= > console.log(res))
function bar() {
console.log('async2 end')}console.log('script end');
Copy the code
Click to see the answer
script start async2 end script end promise1 async1 end error!!! async1 promise2 async1 success Gopal
Topic 6
new Promise((resolve, reject) = > {
console.log(1)
resolve()
})
.then(() = > {
console.log(2)
new Promise((resolve, reject) = > {
console.log(3)
setTimeout(() = > {
reject();
}, 3 * 1000);
resolve()
})
.then(() = > {
console.log(4)
new Promise((resolve, reject) = > {
console.log(5)
resolve();
})
.then(() = > {
console.log(7)
})
.then(() = > {
console.log(9)
})
})
.then(() = > {
console.log(8)
})
})
.then(() = > {
console.log(6)})Copy the code
Click to see the answer
One, two, three, four, five, six, seven, eight, nine
Topic 7
console.log('1');
setTimeout(() = > {
console.log('2');
Promise.resolve().then(() = > {
console.log('3');
})
new Promise((resolve) = > {
console.log('4');
resolve();
}).then(() = > {
console.log('5')})})Promise.reject().then(() = > {
console.log('13');
}, () = > {
console.log('12');
})
new Promise((resolve) = > {
console.log('7');
resolve();
}).then(() = > {
console.log('8')})setTimeout(() = > {
console.log('9');
Promise.resolve().then(() = > {
console.log('10');
})
new Promise((resolve) = > {
console.log('11');
resolve();
}).then(() = > {
console.log('12')})})Copy the code
Click to see the answer
1, 7, 12, 8, 2, 4, 9, 11, 3, 5, 10, 12
conclusion
This article from the TWO characteristics of JS: Single threading and non-blocking introduces the need for event loops because event loops behave very differently in the browser and node.js. I’ll only talk about event loops in the browser, and I’ll introduce microtasks and macro tasks, and how they execute. I’ll finish with seven questions to help you reinforce your knowledge
If you like, don’t forget to like ~ ~
Recommended excellent articles in the past
- 20 Vue skills that a qualified intermediate front end engineer should master
- 【Vue Advanced 】 — How to implement transparent transmission of component properties?
- What the Front end should Know about HTTP
- The most powerful CSS layout – Grid layout
- How to write a complete Vue application in Typescript
- Web debugging tool the front end should know about — Whistle
reference
Detail the Event Loop mechanism in JavaScript
In-depth understanding of NodeJS event loops
Concurrency models and event loops
【 Front end system 】 Talk about understanding EventLoop from an interview question
Philip Roberts: What exactly is an Event Loop? | JSConf 2014 in Europe
Event Loop mechanism in JavaScript
JS event loop mechanism (Event loop) macro task/micro task
Understanding the js event loop (browser)
JS event loop and Macro Micro task queue from interview questions