Hi, I’m Tom. There are so many articles on the Internet analyzing the pure concept of synchronous asynchrony. Today, let’s talk about this issue from an experimental point of view.
JavaScript single-threaded mechanism
To talk about synchronous asynchrony, let’s start with the fact that JavaScript is a single-threaded language.
As we all know, JavaScript is a single-threaded scripting language, everything must be executed in a sequence, so why is JavaScript a single-threaded language? This is related to the application scenario and the original intention of the design. JavaScript is designed as a scripting language running on the client side to facilitate the interaction between the browser and the user. DOM operations must be performed in a certain order through JavaScript. Then things can get pretty messy.
But in today’s processor more and more powerful, single thread is a bit of overuse, so HTML5 proposed the Web work standard, allowing the creation of multiple threads, but the secondary thread is unable to manipulate DOM and must be completely commanded by the main thread, so in fact, JavaScript is still a single thread.
Synchronous asynchrony is introduced to reduce the long wait time caused by a single thread and improve the user experience.
This paper mainly uses timer to test the synchronous asynchronous mechanism.
Event Loop
When performing stack of free time to read the task queue, moved to perform the job of the task queue stack, if you have encountered in processing asynchronous code hang again, execute to task queue when the stack is empty, forming a circle! This is the event loop event loop, here the concept of execution stack and task queue, below will be talked about.
Stack and heap execution
In JavaScript, it’s a single-threaded process of execution, and until the first thing is done, the next thing is waiting, but some things take a long time to execute (such as Ajax requests), and the entire queue is waiting, so the JavaScript founders open up the execution stack and the task queue.
- A piece of JavaScript code placed on the execution stack is parsed line by line, and when it encounters asynchronous code, it suspends the asynchronous code and continues to parse line by line.
- Asynchronous code is suspended and an event is placed on the task queue when the result is received at the end of execution.
- When the execution stack is empty, the system will read the task queue, put all the existing code in the task queue into the execution stack, and continue to execute as synchronous code. (Asynchronous code can be interpreted as synchronous code, since only asynchronous code that already has a result is placed on the task queue)
- If asynchronous code (such as creating a timer inside a timer) is encountered when the execution stack executes the “synchronous code” (previously asynchronous to synchronous), the asynchronous code will continue to hang.
graphic
So let’s say we have this code right here
console.log('tom')
var num = 10
num += 5
setTimeout((a)= >{
console.log('xx')},1000)
Copy the code
Engine preparsing
//var has a variable boost, but how deep
var num = undefined
console.log('tom')
num = 10
num = num + 5
setTimeout((a)= >{
console.log('xx')},1000)
Copy the code
After the pre-parsing is complete, the resulting code is placed in order on the execution stack and then executed from the top down
The most important thing about the execution stack is that it’s always synchronized code, and it’s always executed that way. Even when it comes to Promise microtasks, I think it’s all in sync code. There’s a timer at the end of the code. Most technical articles on timers will simply describe them as asynchronous code, which is not entirely wrong, but just complete.
I think the synchronous asynchronous nature of timers can be fully interpreted as:
Timer trigger is synchronous, timer callback function is asynchronous
So when the code reaches setTimeout in the execution stack, the timer hangs, the timer starts, and the execution stack continues
When the specified 1000ms time is up, the timer callback is pushed into the end of the task queue.
The timer
Here’s what happens when the timer goes off
-
The timer
- When a timer is suspended, the timer is started, and when the timer is finished, the callback function is immediately thrown into the task queue
- When the execution stack is finished, go to the task queue and take it to the execution stack
- Example 1:
const timer = setTimeout(function(){ console.log('I'm a 5s timer, my callback executes.')},5000) const timer = setTimeout(function(){ console.log('I'm a 2s timer, my callback is executing.')},2000) s ... // This is a synchronization code that takes 10 seconds to complete. // The program outputs the output for 10s (note: the output is 10 seconds to 10 seconds) 'I'm a 2s timer, my callback is executing.' 'I'm a 5s timer, my callback executes.' Copy the code
-
Example 1:
-
The timer is suspended as soon as it is detected by the execution stack, and starts timing. Once the timing is complete, the callback function is thrown into the task queue.
-
Because the program executes very fast, 5s timer and 2S timer are executed almost at the same time and suspended to start timing, while 2s timer completes the timing first and is thrown into the task queue due to its short time.
-
When the execution stack is finished (in the example, it takes 10s), the code block will be put into the task queue and put into the execution stack. The principle of queue is first in, first out, so the 2s timer callback function will be executed first, then the 5s callback function will be executed
-
Timers are not necessarily reliable. In the example, both 2s and 5s timers failed to execute their callbacks on time, so the timing of the timer describes when the callback was thrown into the task queue, not when it was executed, so you can assume that the timing of the timer callback is unpredictable.
-
-
Example 2:
const timer = setTimeout(function(){ console.log('I'm the timer for 5s, my callback is executed.') setTimeout(function(){ console.log('I created the timer inside the 5S timer, my callback executed')},5000)},5000)...// This is a synchronization code with a lot of computation, which takes 10 seconds to complete // When running 10s, output: 'I'm the timer for 5s, my callback is executed.' // Another 5s passes 'I created the timer inside the 5S timer, my callback executed' Copy the code
- Example 2 We learn that:
- Is placed in the task queue when the parent timer finishes, while the child timer has not yet been created.
- The child timer is created when the stack completes and the code is retrieved from the task queue and the parent timer callback is executed
- When the asynchronous timer completes the callback and throws it into the task queue and then onto the execution stack, the code block of the callback function is already synchronous code, and the asynchronous code in the callback is still asynchronous code, and should be suspended when suspended
The ajax? Ajax code is also asynchronous code, and what happens when it’s asynchronous
- When the stack executes, it hits ajax code, ok, and suspends, the Ajax request has already been sent, but it hasn’t responded yet
- Once the response is received, the Ajax-executed callback is immediately thrown into the task queue when the response body is retrieved
- When the stack finishes executing, the callback function of the Ajax response is executed when the task queue picks up the code
- What if you have multiple Ajax requests?
- They all get suspended
- Both start sending requests at the moment of their respective suspension
- The callback that receives the response first is thrown to the task queue first
About the authentication
If you want to implement the synchronization code in the example that takes a lot of time (for example, the calculation takes 10 seconds), I recommend using the Fibonacci sequence to help you verify the experiment.
function Fibonacci(step){
if(step==1) {return 1
}
if(step==2) {return 1
}
return Fibonacci(step- 1) + Fibonacci(step2 -)}console.time('Fibonacci')
// Fibonacci sequence 43 is recommended. The performance time is about 4.3s(I7-8550U). The performance time varies with different devices and environments.
console.log(Fibonacci(43))
// Combine console.time and console.timeEnd to calculate the time
console.timeEnd('Fibonacci')
Copy the code
conclusion
The JavaScript execution stack suspends asynchronous code whenever it encounters it, and it doesn’t matter how many asynchronous code suspends or who suspends first, except that whoever responds first gets their callback pushed into the queue first.