This is the 9th day of my participation in the August Wen Challenge.More challenges in August
preface
Dismantling implementation Promise and its surrounding | August more challenges In this paper, a lot of talk about the task of macro and micro point and it is closely related and the event loop mechanism. This article will also explore the details of the event loop mechanism.
Single-threaded language
JavaScript is a single-threaded language, as we all know. So why is JavaScript a single-threaded language, and is it a bad idea to change to multithreaded at all?
Ruan Yifeng senior mentioned the reason for this: JavaScript has been single-threaded since its birth. The reason is probably not to make the browser too complex, since multiple threads share resources and potentially modify each other’s results, which would be too complex for a web scripting language. JavaScript is a single-threaded language. A simple scenario: If two threads are working on a DOM at the same time, one is modifying it, and the other is deleting it, which one is the baseline? To avoid this scenario, JS is single threaded.
The Web worker standard proposed by H5 allows JavaScript to create multiple threads, but the sub-threads are completely controlled by the main thread, so JavaScript itself is still a single thread.
So what are the problems with the JavaScript single-threaded language? Take a single threaded task 🌰 :
let hello = 'hello'
let world = 'world'
console.log(hello + ', ' + world)
Copy the code
After compiling the above code, the JS engine will put all the task code into the main thread. When the main thread starts executing, the tasks will be executed from top to bottom in order until hello, world is printed, and the main thread will exit automatically. Everything is fine
But the reality is complex and brutal 🤦♀️, and it is impossible to follow a routine all the time. What can I do if subsequent tasks are blocked after a task is executed for a long time?
Execution stack and task queue
Single-threaded means that all tasks need to be queued. If the previous task takes too long to execute, the later task will have to wait, such as the IO thread (Ajax request data), to wait for the results to come out before executing. However, this wait is not necessary. We can suspend the waiting task and continue to execute the subsequent task. Therefore, tasks can be divided into two types: one is synchronous tasks; One is asynchronous tasks. Synchronization tasks: all tasks are executed on the main thread, and the execution stack is used to manage the progress of synchronization tasks. Asynchronous task: after the asynchronous operation is completed, the task queue is first entered. When the main thread execution stack is empty, the asynchronous task in the task queue is read.
function helloWorld() {
console.log('inner function')
setTimeout(function() {
console.log('execute setTimeout')
})
}
helloWorld()
console.log('outer function')
Copy the code
Use the Loupe tool to analyze whether the above code is what we say it is.
helloWorld
The function enters the stack and executeshelloWorld
The code inside the function.Console. log(' inside function ')
Go to the execution stack and printWithin the function
.- perform
setTimeout
, is a scheduled task that needs to be delayed, so it is suspended first, and then the anonymous function is queued and the rest of the code on the main thread continues to be executed. Console. log(' out of function ')
Go to the execution stack and printOutside the function
.- After executing the main thread code, read the anonymous function in the task queue and print
execute setTimeout
.
The code execution sequence fits perfectly with the previous conclusion
Event loop
It is called an event loop because the main thread reads events from the task queue in a continuous loop. To better understand the Event Loop, refer to Philip Roberts’ talk “Help, I’m Stuck in an Event-Loop”)
As shown above, when the main thread runs, it generates a heap and a stack, and the code in the stack calls WebAPIs, enqueuing the specified callback function or event when the trigger condition is met. When the code on the stack is finished, it loops through the events in the task queue, and so on.
One more information point to take away from the diagram is that there is not only one type of task in the task queue, it includes things like input events (mouse scroll, click), microtasks, file reads and writes, Websockets, timers, and so on. Input events, file reads and writes, and Websockets are all asynchronous requests that wait for the I/O device to complete. How does a timer specify that code should proceed after a specified time? What are microtasks?
The timer
Timers are mainly composed of setTimeout and setInterval, which are similar but differ in the number of times. The former is executed once, while the latter is executed repeatedly. Take setTimeout as an example. The basic usage is as follows.
function helloWorld() {
console.log('hello world')}let timer = setTimeout(helloWorld, 1000)
Copy the code
Quite simply, the code above prints Hello World after 1000ms using setTimeout. I don’t know if you have any questions? As mentioned above, tasks pushed into the queue are read and executed sequentially, so how can the timer callback function be guaranteed to be called within the specified time? Looking through the materials, I found that there is a concept about designing delay queue in Chromium, and the tasks in delay queue are calculated according to the launch time and delay time. If the task expires, it completes the expired task and then loops again. When using timers, there are other precautions 💢 If the execution time of the main thread task is too long, the execution of the timer task will be affected.
function helloWorld() {
console.log('hello world')}function main() {
setTimeout(helloWorld, 0)
for(let i = 0; i < 5000; i++) {
console.log(i)
}
}
main()
Copy the code
In the code above,setTimeout
The function sets a zero-delay callback function, but the callback cannot be called until 5000 cycles have been executed. To viewPerformance
Panel to performhelloWorld
It’s almost late400
Ms, as shown in the following figure.
If the timer has nested calls, the system sets the minimum interval to4ms
function helloWorld() { setTimeout(helloWorld, 0)}
setTimeout(helloWorld, 0)
Copy the code
Chrome
If the timer is called more than 5 times nested, the current method is blocked if the interval is less than4ms
, sets each interval to4
Ms. As shown in the figure below.
If the page is not active, the minimum timer interval is 1000ms. The purpose is to optimize the load loss and reduce power consumption.
Chrome, Safari, and Firefox all use a 32-bit storage delay. Therefore, a maximum of 2^ 31-1 = 2147483647(ms) can be stored. 31 because the highest bit of binary is a sign bit, -1 because there is a 0.
Macro and micro tasks
Understand the micro task, that macro task also have to figure out is not ~. The following table shows the related technologies of macro task and micro task.
Macro task | Micro tasks |
---|---|
setTimeout | MutationObserver (it) |
setInterval | Process. NextTick (node) |
I/O, event | Promise.then/catch/finally |
SetImmediate (node) | queueMicrotask |
Script (whole block of code) | |
requestAnimationFrame | |
PostMessage, MessageChannel |
When do macro and micro tasks perform?
Macro task: A new task is added to the end of the task queue, and a callback function is executed when the loop executes the task. Microtask: The callback function is executed before the current macro task completes.
Execution timing can be seen: each macro task is associated with a microtask queue. The execution sequence can be obtained as follows: execute macro task first, and then execute the micro-task under the current macro task. If the micro-task generates a new micro-task, continue to execute the micro-task. After completing the micro-task, continue the event cycle of the next macro task.
Practice is the sole criterion for testing truth. Take a Promise’s 🌰
console.log('start')
setTimeout(function() { / / macro task
console.log('setTimeout')},0)
let p = new Promise((resolve, reject) = > {
console.log('Initialize Promise')
resolve()
}).then(function() {
console.log('internal Promise1') Micro / / task
}).then(function() {
console.log('internal Promise2') Micro / / task
})
p.then(function() {
console.log('external Promise1') Micro / / task
})
console.log('end')
Copy the code
script
Macro task, start executing code, printstart
.- encounter
setTimeout
Macro task, queued to wait for the next event loop. - encounter
Promise
Execute immediately, printInitialize the Promise
. - encounter
new Promise().then
Microtask, inscript
A microtask queue for a macro task waiting for the current macro task to complete. - encounter
p.then
Microtask, inscript
A microtask queue for a macro task waiting for the current macro task to complete. - print
end
, the currentscript
The macro task is complete. - View the current
script
The microtask queue of the macro task, the queue is not empty, and the current queue head is extractednew Promise().then
, perform printingInternal Promise1
Come across againthen
Microtask, then proceed to printInternal Promise2
Execute complete, exit team. script
The microtask queue under the macro task is not emptyp.then
, perform printingExternal Promise1
And out of the team.script
The microtask queue under the macro task is empty, and the next macro task starts.- Executing macro tasks
setTimeout
printsetTimeout
. Check that the task queue is empty and the program ends.
reference
What is an Event Loop in JavaScript
Small tools
Turn video GIF