Browser Thematic series – Event loops
preface
Let’s start with a brief talk about JavaScript and other things related to this topic to deepen the reader’s absorption and understanding of the content
Interpretive language
Scripting languages are computer programming languages created to shorten the traditional edit-compile-link-run process
Scripting languages (C/C ++, Java) are often called interpreted languages because their code is interpreted line by line rather than compiled
So javascript, like Python and the shell, is an excellent interpreted language
One of the performance bottlenecks for interpreted languages is the interpreter, and javascript has excellent interpreter engines such as V8 (Android, Chrome) and JSCore (IOS, Safari), which have contributed to the widespread adoption of JS
Single threaded model
One of the hallmarks of the javascript language is single-threaded, which means you can only do one thing at a time
Why single thread?
As a browser scripting language, javascript’s primary purpose is to interact with users and manipulate the DOM, which means that it has to be single-threaded, or it can cause complex synchronization issues
For example, if you have two threads of javascript, one thread adds content to a DOM node, and the other thread removes it, the browser doesn’t know which thread to use.
So, to avoid complexity, javascript has been single-threaded since its inception
Advantages of single threads
- There are no deadlocks caused by contention between threads for resources
- All code is executed synchronously
- There is no resource overhead of thread switching
Disadvantages of single threading
- Single threading means that all tasks need to be queued until the first one is finished before the next one can be executed. If the first task takes a long time, the second task has to wait forever
Task queue
There are scenarios in browsers where there are many time-consuming tasks, such as Network requests (Ajax), listening for event delivery, timers, and so on
The designers of the javascript language realized that the main thread could simply ignore the IO device, suspend the pending task and run the next one first. Wait until the IO device returns the result, then go back and continue the pending task
So all the tasks are divided into synchronous tasks and asynchronous tasks
Synchronization task
A queued task on the main thread can be executed only after the previous task is completed
Sequential execution
Asynchronous tasks
A task that does not enter the main thread but enters the task queue
An asynchronous task is executed on the main thread only when the task queue notifies the main thread that it is ready to execute
When the interpreter engine encounters an asynchronous task, it suspends it and, when the time is right, puts its callback function on the task queue
For example, like a stir-fry, the contents of the wok are constantly being stir-fried (the main thread), and various ingredients (different asynchronous tasks) are only added to the wok as needed to fulfill their mission
See the Browser theme series – Browser Kernel for more information on how the engine is made up
Asynchronous mechanisms
- All synchronization tasks are executed on the main thread, forming an execution stack
- In addition to the main thread, there is a task queue in which an event is placed whenever an asynchronous task has a result
- Once all synchronization tasks in the execution stack have been executed, the system reads the “task queue” to see what events are in it. Which corresponding asynchronous tasks, then end the wait state, enter the execution stack, start execution
Whenever the main thread is empty, it reads the “task queue”, that’s how javascript works. The main thread repeats step 3 above
Events and callbacks
A “task queue” is a queue of events. When an IO device (mouse, keyboard, etc.) completes a task, an event is added to the “task queue”, indicating that the related asynchronous task can be added to the “execution stack”. The main thread reads the “task queue”, which is to read what events are in it
As long as the callback function is specified, these events (mouse clicks, keyboard keystrokes, page scrolling, etc.) occur in a “task queue” waiting for the main thread to read
The “callback functions” are those that are suspended by the main thread. Asynchronous tasks must specify a callback function, which is executed when the main thread starts executing an asynchronous task
Queues have a first-in, first-out feature, and the main thread reads the first event in the task queue first
The main thread is read automatically. As soon as the stack is empty, the first position in the task queue is automatically sent to the main thread
For timer events, the main thread checks the execution time and returns to the main thread only after a specified time, that is, after a certain period of time, the corresponding callback function of the event is added to the execution stack
Event loop
What is an Event Loop
This is commonly known as the event loop
Event Loop is an execution model with different implementations in different places (in different languages)
The js event loop is responsible for executing code, collecting and processing events, and executing subtasks in the queue, which is quite different from other language models
One of the most interesting features of js’s event loop model compared to many other languages is that it never blocks, and processing I/O is usually performed through events and callbacks
So while an application is waiting for an AJAX request to return, it can still handle other things, such as user input, mouse clicks/scrolls, etc
What is an execution stack
The execution stack can be thought of as a stack structure for storing function calls, following the principle of first in, last out
Js will first create a main function when executing the code, and then according to the code executed, according to the principle of “first in, last out”, the last function will be the first to pop the stack
Here is an online tool for visualizing the execution stack -> Loupe
The sample
function a(v){
return v*4
}
function b(v){
return a(v*3)}console.log(b(2))
Copy the code
Into the stack order
1. main()
2. console.log(b(2))
3. b(2)
4. a(6)
Copy the code
The stack order
1. a(6) / / 24
2. b(2) / / 24
3. console.log(b(2)) / / 24
4. main()
Copy the code
When using recursion, there is a limit to how many functions can be stored on the stack. If too many functions are stored and not released, the stack will burst (as shown in the figure below).
Event Loop in browser
Through the above elaboration, probably also know how js is executed, understand how to process asynchronous tasks in the way of single line step execution mechanism, the following begins to describe the process of execution in detail
Is when you execute javascript code to implement the stack into the function/callback function, when faced with asynchronous code, can be hung and when you need to perform the join the task queue (many), once the execution stack is empty, the mechanism of time cycle Will take out need to execute code from the task queue and executed in the execution stack
So asynchrony in JS is essentially still a synchronous behavior
Task source
Different task sources are grouped into different task queues
Micro tasks
Micro tasks (microtask) : jobs
- promise
- MutationObserver
- .
Macro task
Macro task (macrotask) : the tasks
- script
- xhr
- setTimeout
- setInterval
- requestAnimationFrame
- I/O
- UI rendering
- .
Event Loop Execution sequence
This first throws up the order of execution of the different tasks in the browser’s JS event loop
Each iteration of an event loop is called a tick
- Execute all synchronization code
- After all synchronous code is executed, the execution stack is empty. Then, check whether asynchronous tasks need to be executed
- perform
Micro tasks
If another microtask is generated during the execution of the microtask, it will be added to the end of the microtask queue and will be called to execute in this cycle - After performing all the microtasks, render the page if necessary:
- Determine whether the document needs to be updated
- Most display devices still have a refresh rate of 60Hz, so it takes 16.6ms to update a render
- Check whether there is a resize or scroll event. If there is, the event will be triggered
- Therefore, resize and Scroll events will be triggered at least once in 16.6ms, that is, with throttling function.
- Determine whether a media query is triggered
- Update the animation and send the event
- Check whether a full-screen operation event exists
- Execute the requestAnimationFrame callback
- The IntersectionObserver callback is executed to determine whether elements are visible and can be used for lazy loading
- Update the interface
- Determine whether the document needs to be updated
- Start the next Event Loop, take one execution from the macro task, and then the micro task…
Summary induction
- A macro task executes one task at a time from the macro task queue, and then executes the tasks in the microtask queue
- All tasks in the microtask queue will be taken out and executed until the microtask queue is empty.
- When performing UI rendering, the time node is after performing all microtasks and before the next macro task
- Timers are not infallible
- SetTimeout/SetInterval simply puts its callback function into the macro task queue after the specified time
The sample
Here is an example to illustrate the sequence of code execution
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')}async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() {
console.log('setTimeout')},0)
new Promise(resolve= > {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')})console.log('script end')
Copy the code
- Perform synchronous code output
script start
- console.log(‘script start’)
- Execute synchronous code output in async1
async2 end
- async1()
- async2()
- Because the function has the async flag, it returns a Promise, which we’ll call P1
- console.log(‘async2 end’)
- await
- Await is an indication of a thread giving away an await async2() and then returning outside async1
- “Await” is the syntactic sugar of generator plus Promise, and automatic execution generator is implemented internally, so there is another layer of Promise, which we call P2 and which is wrapped with P1
- When we encounter an asynchronous task timer, we suspend it and call it S1
- Execute the new Promise constructor and print
Promise
- console.log(‘Promise’)
- resolve()
- The generation of new microtasks is denoted as P3
- Perform synchronous code output
script end
- At this point, all synchronous code execution is completed, and the situation of microtask and macro task queue is respectively
- Micro tasks: [P2, P3)
- Macro task: (S1)
- Perform all microtasks
- Take out P2 and execute it to generate new microtask P1 and add it to queue [P3,P1]
- Take out theP3Execute, print out
promise1
And generate new microtasksP4Join a queue [P1.P4] - Take out P1 and execute without output, generate new microtask P5 and add it to queue [P4,P5]
- Take out P4 execute, output
promise2
, no new tasks are generated [P5] - Take P5 to execute, output
async1 end
, no new task is generated []
The result for the lower version browser is
// script start --> async2 end --> Promise --> script end --> promise1 --> promise2
// async1 end --> setTimeout
Copy the code
Since await is followed by Promise, async1 end needs to wait 3 microticks to execute
Async1 its equivalent before v8 optimization of the old version of the code is
function async1(){
new Promise((resolve) = >{
const p = new Promise(res= >res(async2()))
p.then(() = >{
console.log('async1 end')
resolve()
})
})
}
Copy the code
The result of the new browser is
// script start --> async2 end --> Promise --> script end --> async1 end
// promise1 --> promise2 --> setTimeout
Copy the code
In this case, the above P2 package P1 is merged into one, i.e., await after Promise will not be wrapped again
Async1 its equivalent v8 optimized code is
function async1(){
new Promise((resolve) = >{
const p = Promise.resolve(async2())
p.then(() = >{
console.log('async1 end')
resolve()
})
})
}
Copy the code
summary
- In the new browser, await promiseFun, 3 Microticks are optimized for 2 microticks
- New Promise is replaced by promise.resolve
- The promise. resolve argument returns the Promise directly if it is a Promise
supplement
Problem tracing can be viewed
- Faster asynchronous functions and promises
- How does V8 implement faster await? Have a deep understanding of the operation of await
self-test
Since the test 1
console.log(1);
setTimeout(() = > {
console.log(2);
Promise.resolve().then(() = > {
console.log(3)}); });new Promise((resolve, reject) = > {
console.log(4)
resolve(5)
}).then((data) = > {
console.log(data);
})
setTimeout(() = > {
console.log(6);
})
console.log(7)
Copy the code
Click to see the answer
The output
// 1 4 7 5 2 3 6
Copy the code
Self-test 2
console.log(1);
setTimeout(() = > {
console.log(2);
Promise.resolve().then(() = > {
console.log(3)}); });new Promise((resolve, reject) = > {
console.log(4)
resolve(5)
}).then((data) = > {
console.log(data);
Promise.resolve().then(() = > {
console.log(6)
}).then(() = > {
console.log(7)
setTimeout(() = > {
console.log(8)},0);
});
})
setTimeout(() = > {
console.log(9);
})
console.log(10);
Copy the code
Click to see the answer
The output
// 1 4 10 5 6 7 2 3 9 8
Copy the code
reference
- Detailed explanation of JavaScript operation mechanism: Talk about Event Loop again
- MDN: Concurrency model and event loop
The original text is first published in personal blog, the special series will sort out many articles, and we learn together, common progress, if there is a mistake, please correct
Browser series
- 1. Browser Thematic series – Caching Mechanisms
- 2. Browser Thematic series – Principles of rendering
- 3. Browser Thematic series – Block rendering
- 4. Browser Thematic Series – Local Storage
- 5. Browser Special Series – Web Security
- 6. Browser Series – Browser kernel
- 7. Browser Thematic series – Cross domain and cross site
- 8. Browser Thematic Series – Event loops