preface
This article is to talk about what is the process of browser reading a script code, how to deal with asynchronous code, how to deal with macro tasks and micro tasks.
A couple of concepts before we get started.
Stack (lifO)
To start with a stack model, function calls form stack frames.
function f1() {
f2();
}
function f2() {}
f1();
Copy the code
This code, for example, creates the first frame when calling f1; The second frame is created when f1 calls F2. The second frame is placed on top of the first frame. When F2 is finished, the top layer of the second frame pops out of the stack. When F1 is finished, the top layer of the first frame pops out of the stack and the stack is empty. It’s called last in, first out.
This stack, also known as the execution stack, executes the tasks in the task queue.
Queue (first in, first out)
And then a little bit about queues. There are tasks in the queue.
If a new task (such as a click event triggered by the user) is queued, it is queued to the back.
The tasks in the queue are executed in the execution stack.
There are two types of queues: macro task queues and micro task queues. There are two kinds of tasks: macro tasks and micro tasks.
A MacroTask (or MacroTask)
Host-initiated tasks.
Such as script, setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering that contain code
Microtasks
Tasks initiated by the JavaScript engine are micro tasks. Microtasks are equivalent to urgent tasks, and the time loop will process the microtasks first.
Such as Process. NextTick, Promises, Object. Observe, MutationObserver
Event loops in browsers
The main thread reads events from the “task queue” in a continuous Loop, so the whole operation mechanism is also called an Event Loop.
If asynchronous code is encountered during stack execution, it will be removed from this execution and added to another thread. After processing, the thread will add the task to the end of the corresponding task queue. For example, setTime delay will be added to the macro task queue after it ends, and promise will be added to the micro task queue after it completes execution.
The relationship between event loops, macro tasks and micro tasks is shown in the figure below:
The entire block of code for the script tag is the first execution task on the execution stack.
Let’s look at a piece of code
Test: macro task micro task execution sequence
setTimeout((a)= > console.log(1))
console.log(2);
new Promise((resolve, reject) = > {
console.log(3)
resolve();
}).then((a)= > console.log(4))
console.log(5)
Copy the code
Execution process:
- Execute the first macro task (whole block of code)
- SetTimeout is encountered, skipped (added to macro task queue after delay ends)
- Print 2
- New Promise is synchronous code, print 3, then is asynchronous, skip promise.then(added to micro task queue after completion)
- Print 5
- Perform the end
- There are executable microtasks, start microtasks
- Print 4
- Perform the end
- No microtask available, start macro task
- Print 1
- Perform the end
- No microtasks, no macro tasks.
So the execution result is: 2, 3, 5, 4, 1
The second parameter is the minimum delay time
var t = +new Date(a); setTimeout((a)= > {
console.log('Timer 2 seconds, actual time 3 seconds');
}, 2000)
setTimeout((a)= > {
console.log('Timer 1 seconds, actual time 3 seconds');
}, 1000)
while (+new Date() - t < 3000) {} // Delay 3 seconds
Copy the code
In the first second, the second setTimeout inserts the macro task queue. Second, the first setTimeout is inserted into the macro task queue; The third second
- First second: the second setTimeout inserts the macro task queue;
- Second: The first setTimeout inserts the macro task queue;
- Third second: code execution is complete;
- There is no executable micro task, execute the macro task and output ‘timer 1 seconds, the actual time is 3 seconds’, and the code execution ends;
- There is no executable micro task, execute the macro task and output ‘Timer 2 seconds, the actual time is 3 seconds’, and the code execution ends;
You can see that setTimeout is inserted into the macro task queue immediately after the delay ends.
Therefore, the execution result is as follows: Timer 1 seconds, and the actual time is 3 seconds. Timer 2 seconds, and the actual time is 3 seconds
Test: the macro task is completed before the micro task
setTimeout((a)= > console.log(1))
fetch('https://deployment.whosmeya.com/api/getok')
.then((a)= > console.log(2))
var t = +new Date(a);while (+new Date() - t < 2000) {} // Delay two seconds
Copy the code
Although the macro task setTimeout is completed before the promise of the micro task, after the execution of the first macro task (the whole block of code), there is an executable micro task, so the micro task is executed first.
Therefore, the execution result is: 2 1
Test: macro tasks contain microtasks
setTimeout((a)= > console.log(1))
new Promise(function (resolve, reject) {
setTimeout((a)= > resolve())
}).then((a)= > console.log(2))
Copy the code
After the first macro task (code block) is executed, there are two macro tasks in the macro task queue, so the first setTime is executed first. The execution sequence of stack tasks is: code block, first setTimeout, second setTimeout, promise.then.
The result is 1, 2
conclusion
- Javascript is single-threaded meaning the main thread is a single thread, and in addition to that you have rendering threads, event threads, etc.
- The Event loop always listens to the task queue and puts the task on the execution stack.
- Micro tasks, like urgent tasks, are always done first.
- When asynchronous code is encountered in the execution stack, it will be executed by other threads and inserted into the corresponding task queue by the thread after execution.
reference
- This time, thoroughly understand the JavaScript execution mechanism
- Specification and implementation of Event Loop
- More on the Event Loop
- Concurrency model and the event loop
- whatwg event loops
- whatwg timers