Event Loop
Process
It is the basic unit for resource allocation and scheduling by the operating system, and the dynamic process of program execution.
Thread
It is the smallest unit that an operating system can schedule for computation. It is contained within a process and is the actual operating unit within the process.
Browser Process Model
In a browser, there are many processes. The main ones are:
Browser process: responsible for page display, user interaction, sub-process management, etc. The browser process internally starts multiple threads to handle different tasks.
Network process: responsible for loading network resources; internally starts multiple threads to handle different network tasks.
Rendering process: after the rendering process starts, it opens a main rendering thread, responsible for executing HTML/CSS/JS code. By default, each tab opens a new rendering process to ensure different tabs do not affect each other.
The main rendering thread is the thread we are most concerned about.
Main rendering thread workflow:
Parse HTML
Parse CSS
Calculate styles, layout
Process layers
Render 60 times per second
Execute global JS code
Execute event handler functions
Execute timer callback functions
…
So how should the main thread schedule?
The main rendering thread enters an infinite loop.
Each loop checks whether there is a task in the message queue; if so, it takes out one task to execute, and after execution, enters the next loop.
All other threads, including threads from other processes, can add tasks to the message queue at any time.
This scheduling process is called the Event Loop.
Example:
btn.onClick = ()=>{
h1.innerText = 'new text'
sleep() // synchronous blocking
}
What happens with this code? The interaction thread listens for button click events. When a click occurs, it wraps the callback function into a task object and puts it into the message queue. The main thread takes the task from the task queue and executes it. When it executes the sleep function, it waits for several seconds. After the sleep function finishes, the page is rendered.
Task Queues
Each task has a task type, and tasks of the same type must be in the same queue. Tasks of different types can be in different queues.
Browsers must have a microtask queue, where the priority of tasks in the microtask queue is higher than other queues. As browser complexity increases, W3C no longer uses the term 'macrotask queue'. Instead, there are several other queues.
Currently, Chrome's implementation includes at least the following queues:
Delay queue: used for callback tasks after timers expire, priority is medium.
Interaction queue: used for event handling tasks generated after user operations, priority is high.
Microtask queue: priority is [highest].
Summary
Single threading is the reason for asynchrony, and the event loop is the implementation of asynchrony.
Regarding requestAnimationFrame (rAF), it has its own independent queue (often called the Animation Frame Callbacks Queue), which is called at the start of a rendering update. It can be simply understood as executing after microtasks have completed.
Test Questions
What does the following code output?
console.log('A (synchronous)');
setTimeout(() => {
console.log('B (macrotask1)');
Promise.resolve().then(() => {
console.log('C (microtask1 - from macrotask1)');
});
}, 0);
Promise.resolve().then(() => {
console.log('D (microtask1)');
setTimeout(() => {
console.log('E (macrotask2)');
Promise.resolve().then(() => {
console.log('F (microtask2 - from macrotask2)');
});
}, 0);
});
(async () => {
console.log('G (async synchronous part)');
await Promise.resolve();
console.log('H (after async await - microtask)');
setTimeout(() => {
console.log('I (macrotask3)');
Promise.resolve().then(() => {
console.log('J (microtask3 - from macrotask3)');
});
}, 0);
})();
Promise.resolve().then(() => {
console.log('K (microtask2)');
});