Refer to an article on how to do it (Js async, event loops and message queues, microtasks and macro tasks)
Single versus multithreading in browsers
JavaScript is a single-threaded, asynchronous, non-blocking, parse-type scripting language.
JavaScript single threading refers to the fact that there is only one thread in the browser responsible for interpreting and executing JavaScript code, namely the JS engine thread. But the browser render process provides multiple threads, as follows:
- JS engine thread (render parsing JS)
- The timer listens to the thread
- HTTP network thread
- DOM events listen to trigger threads
- GUI rendering thread
When it encounters tasks such as timers, DOM event listeners, or network requests, the JS engine sends them directly to the WebAPI, which is the corresponding thread provided by the browser (such as timer thread for setTimeout, asynchronous HTTP request thread for network requests), and the JS engine thread continues with other synchronization tasks. This enables asynchronous non-blocking.
JS engine threads encounter asynchrony (DOM event listeners, network requests, setTimeout timers, etc.) , will go to the corresponding thread to go alone to maintain asynchronous tasks, waiting for a time (end of the timer, the network request is successful, the user clicks the DOM), and then thread is triggered by the event Asynchronous corresponding callback function will be added to the message queue (in the event queue), the callback function for event loop mechanism in the message queue to query and executed.
Conclusion: Browser is multithreaded, but js engine is single-threaded, js when synchronization task, if there are any asynchronous tasks, will be handed over to other threads to deal with, wait for the asynchronous task trigger and have run results, the callback function as a task to join the event queue, and then wait for js thread synchronization task to complete, Then according to the event loop mechanism, asynchronous tasks are taken out from the event queue for execution.
So how exactly does the event queue and event loop work?
Event queues and event loops
- All synchronization tasks are executed on the main thread, forming an execution Context stack.
- In addition to the main thread, there is a “task queue”. Whenever an asynchronous task has a result, an event is placed in the “task queue”.
- Once all synchronous tasks in the “execution stack” are completed, the system will read the “task queue” to find the events and corresponding asynchronous tasks. If there are asynchronous tasks, the waiting state is ended, and the asynchronous tasks are put into the execution stack to start execution.
- The main thread repeats step 3 above.
This is the general process of event queuing and event cycling. In this process, asynchronous tasks are divided into asynchronous macro tasks and asynchronous micro tasks
Asynchronous MacroTasks
- Timers (setTimeout, setInterval)
- DOM events
- HTTP requests (Ajax, FETCH, JSONP…)
Asynchronous microtasks
- Promise (resolve/reject/then…).
- async await
- requestAnimationFrame
- The process. NextTick (process in node. NextTick takes precedence over Promise)
The event loop execution mechanism looks like this:
- Perform a macro task (fetch from event queue if not in stack)
- If a microtask is encountered during execution, it is added to the task queue of the microtask
- Execute all microtasks in the current microtask queue immediately after the macro task is executed (in sequence)
- When the macro task completes, the render is checked, and the GUI thread takes over
- After rendering, the JS engine thread proceeds to the next macro task (retrieved from the macro task queue)
Summary: The js main thread starts the synchronization code as a macro task. When a Promise is encountered during the macro task, it creates a microtask (e.g. the callback in.then()) and adds it to the end of the microtask queue. After a macro task is executed, all microtasks generated during its execution are completed and the next macro task is started before re-rendering and starting the next macro task. This is the event loop.
Give examples of event queues and event loops
setTimeout(function () {
console.log(1);
}, 1000);
console.log(2);
new Promise(resolve= > {
console.log(3);
resolve();
console.log(4);
}).then(() = > {
console.log(5);
}).then(() = > {
console.log(6);
});
console.log(7);
const fn = () = > {
console.log(9);
};
(async function () {
console.log(8);
await fn();
console.log(10);
await fn();
console.log(11); }) ();console.log(12);
Copy the code
Step by step
setTimeout(function () {
console.log(1);
}, 1000);
Copy the code
The browser sets up a macro task, which we’ll call task 1. Start the timer and notify the main thread to execute the callback function when it reaches 1000ms. The browser will queue task 1 in an asynchronous macro task queue and start a listening thread for timing.
console.log(2);
Copy the code
Print 2 immediately and synchronize the execution
new Promise(resolve= > {
console.log(3);
resolve();
console.log(4);
})
Copy the code
new Promise
Will be immediatelyExecute the callback functionexecutor
, so it prints3
。resolve()
Immediately changes the state of the Promise instance to Success'fulfilled'
And at the same timeMicrotask queueCreate a task that we call itTask 2: Be able to put the post based onthen
Way to put it inonfulfilledCallback
Notify the browser to execute (if the method has not been executed yet)- Then the output
4
The above steps are shown below:
.then(() = > {
console.log(5);
})
Copy the code
The callback in.then is also a microtask, assuming task 3. Since task 2 is directly synchronized to resolve(), task 2 will not be placed on the microtask queue and will be skipped, instead putting the determinate state of task 3 on the microtask queue
.then(() = > {
console.log(6);
});
Copy the code
Here. Then the callback is also a small task, hypothesis is task 4, here is not immediately put the micro tasks in the micro task queue, because the task 3 has not been performed, may not know their state, so belong to the temporary state, when to wait for the above 3 task execution, determine the status of the promise, Before deciding whether to perform the microtask
console.log(7);
Copy the code
Direct output 7
The legend for the steps above:
const fn = () = > {
console.log(9);
};
Copy the code
Declare functions
console.log(8);
await fn();
Copy the code
When it encounters an immediate function, print 8
Await:
‘await’ can be written as a promise, so all code under the first ‘await’ in the current context is treated as a callback. So:
Executing fn immediately is equivalent to executing the Promise executor, which is resolve by default, so all code under await is treated as a new asynchronous microtask (task 5) in the current context and placed on the task queue. Similar to the callback in.then in task 3
So now the microtask queue only has task 3, task 5.
So the following code will not be executed for now, and it will continue to be executed
console.log(12);
Copy the code
The output of 12
At this point, the synchronous tasks are done, the main thread is free, and the asynchronous task is executed. Because JS is single-threaded, if the synchronous task is not finished, that is, the main thread is not idle, no matter whether the asynchronous task reaches the stage of execution (such as setTimeout(()=>{},0)), it cannot be executed.
This process is illustrated below
Next, it enters the asynchronous queue and starts executing the asynchronous task.
Here’s how it works:
-
Task 2 will skip task 3 and add it directly to the microtask queue. Since task 3 is not running yet, whether task 4 should be added to the microtask queue depends on the status of task 3 after running. All code below “await” task 5 will be queued in the first round of microtask and will be executed as task 3. So the first iteration of the event loop is task 3, then task 5, so output 5, 10, 9 (second fn). “Then” and “await” are essentially the same
-
When task 3 is performed, there is a new task, task 4, which is then. When I execute task 5, I get await again, so the next code creates task 6 again
-
So the second event loop will execute task 4, task 6, output 6, 11 respectively
-
Finally, execute the macro task and print 1
In this way, each time the task is taken from the event queue, and then executed, and then queried in the event queue after execution, this is the event loop mechanism
To summarize the implementation mechanism of.then
Perform p.t hen (onfulfilledCallback onrejectedCallback)
- I passed it in first
onfulfilledCallback
andonrejectedCallback
stored - Second, verify the state of the current instance
- If the instance state is
'pending'
, no processing is done ('pending'
Note The operation to change the status is asynchronous. - If it has become
'fulfilled/rejected'
(indicates that the operation to modify the status is synchronous.), the corresponding callback function is notified to execute. But instead of executing it immediately, it is placed in the microtask queue in the EventQueue.
- If the instance state is
Promises themselves are not asynchronous and are used to manage asynchrony, but then methods are asynchronous “microtasks”
An example of synchronizing state changes:
let p = new Promise((resolve, reject) = > {
console.log(1);
resolve('OK'); //=> Synchronize the status and result
console.log(2);
});
console.log(p); // The status is changed to Succeeded
p.then(result= > {
console.log('success -- >', result);
});
console.log(3);
Copy the code
Examples of asynchronous state changes:
let p = new Promise((resolve, reject) = > {
console.log(1);
setTimeout(() = > {
resolve('OK');
// change the state and value of the instance to "sync"
// + execute "asynchronous microtask: also place the execution method in the microtask queue in EventQueue"
console.log(p);
console.log(4);
}, 1000); //=> Stores an asynchronous macro task
console.log(2);
});
console.log(p);
// When onledCallback is received, the state is still pending, and only the method is stored
p.then(result= > {
console.log('success -- >', result);
});
console.log(3);
// Wait 1000ms, execute the timer function "take out the asynchronous macro task to execute"
Copy the code
setTimeout
Store an asynchronous macro task. After 1000ms, the function in the timer is executed, that is, the asynchronous macro task is taken out and executed- When the first p is printed, accept it now
onfulfilledCallback
When the state is stillpending
At this point, only the method is stored - After a second,
resolve('OK');
When:-
Synchronously changes the state and value of the instance
-
Onculedcallback performs “asynchronous microtasks: also put the things that execute the method into the microtask queue in EventQueue” based on the then stored onculedCallback before notification
-
Second example (then chain) :
const p1 = new Promise((resolve, reject) = >{
resolve('p1')})const p2 = new Promise((resolve, reject) = >{
setTimeout(() = >{
resolve('p2')},1000)})const p3 = new Promise((resolve, reject) = >{
resolve('p3')
})
p1.then((res) = >{
console.log(1)
}).then((res) = >{
console.log(2)
})
p3.then((res) = >{
console.log(3)
}).then((res) = >{
console.log(4)
})
p2.then((res) = >{
console.log(res)
})
Copy the code
1,3 are executed first when the first round of microtasks are executed. When the first round of microtasks are executed, 2,4 are added to the microtask queue, so 2,4 are executed again. Finally, execute the P3 macro task
This article refers to an article on how to fix (Js async, event loops and message queues, microtasks and macro tasks)