April 2, 2025

Deep dive vào Event Loop và bất đồng bộ trong JavaScript
Tìm hiểu cách Event Loop hoạt động và cách xử lý bất đồng bộ trong JavaScript.
1. Event Loop là gì?
JavaScript là một ngôn ngữ đơn luồng (single-threaded), có nghĩa là nó chỉ có một luồng chính để thực thi code. Để xử lý tác vụ bất đồng bộ (asynchronous) mà không chặn chương trình, JavaScript sử dụng Event Loop.
Event Loop là cơ chế giúp JavaScript quản lý các tác vụ bất đồng bộ như setTimeout, fetch, event listener, và promises bằng cách điều phối các hàng đợi (queues) khác nhau.
2. Call Stack, Web APIs, Task Queue và Microtask Queue
Call Stack (Ngăn xếp gọi hàm)
Call Stack là nơi JavaScript thực thi các hàm theo nguyên tắc LIFO (Last In, First Out).
function foo() { console.log("Foo"); } function bar() { foo(); } bar();
Quá trình thực thi:
bar()
được đưa vào Call Stack.bar()
gọifoo()
,foo()
được đưa vào Call Stack.foo()
thực thi xong, bị pop khỏi Call Stack.bar()
thực thi xong, bị pop khỏi Call Stack.
Web APIs
Các hàm như setTimeout
, fetch
, và event listeners không chạy trong Call Stack mà được đưa đến Web APIs.
console.log("Start");
setTimeout(() => console.log("Timeout"), 0);
console.log("End");
Output:
Start
End
Timeout
Dù setTimeout
có thời gian 0ms, nó vẫn không chạy ngay lập tức mà được đưa vào Web APIs rồi Task Queue.
Task Queue (Macro-task Queue)
Task Queue chứa các macro-task như:
setTimeout
setInterval
setImmediate
(Node.js)I/O Callbacks
Event Loop chỉ lấy một task từ Task Queue mỗi lần và đưa vào Call Stack nếu Call Stack trống.
Microtask Queue (Priority Queue)
Microtask Queue có độ ưu tiên cao hơn Task Queue, chứa:
- Promises (resolve, reject)
- MutationObserver
- queueMicrotask
console.log("Start");
setTimeout(() => console.log("Timeout"), 0);
Promise.resolve().then(() => console.log("Promise"));
console.log("End");
Output:
Start
End
Promise
Timeout
Microtask (Promise.resolve()
) luôn chạy trước Task (setTimeout
).
3. Cách Event Loop vận hành
- Thực thi Call Stack (synchronous code).
- Xử lý tất cả microtasks (Promise, queueMicrotask, MutationObserver).
- Xử lý một task từ Task Queue (setTimeout, I/O, events).
- Lặp lại quá trình trên.
Event Loop tiếp tục chạy cho đến khi không còn code nào để xử lý.
4. Async/Await và Event Loop
async/await
giúp code bất đồng bộ dễ đọc hơn nhưng vẫn tuân theo Event Loop.
async function fetchData() {
console.log("Start Fetch");
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("End Fetch");
}
console.log("Before Fetch");
fetchData();
console.log("After Fetch");
Output:
Before Fetch
Start Fetch
After Fetch
End Fetch
Giải thích:
fetchData()
gọiconsole.log("Start Fetch")
→ Call Stack.await
tạm dừngfetchData()
, trả control lại Event Loop.console.log("After Fetch")
chạy.- Sau 1s,
console.log("End Fetch")
chạy.
5. Tổng kết
✅ JavaScript là single-threaded nhưng có thể xử lý bất đồng bộ nhờ Event Loop.
✅ Microtasks (Promises) luôn chạy trước Macro-tasks (setTimeout, I/O).
✅ Async/Await giúp xử lý bất đồng bộ dễ đọc hơn nhưng không thay đổi cơ chế Event Loop.
Hiểu rõ Event Loop giúp bạn tối ưu hiệu suất ứng dụng JavaScript và tránh các lỗi liên quan đến bất đồng bộ! 🚀
Deep Dive into Event Loop and Asynchronous Programming in JavaScript
Understand how the Event Loop works and how to handle asynchronous operations in JavaScript.
1. What is the Event Loop?
JavaScript is a single-threaded language, meaning it has only one main thread to execute code. To handle asynchronous tasks without blocking the program, JavaScript uses the Event Loop.
The Event Loop is the mechanism that allows JavaScript to manage asynchronous tasks like setTimeout, fetch, event listeners, and promises by coordinating different queues.
2. Call Stack, Web APIs, Task Queue, and Microtask Queue
Call Stack
The Call Stack is where JavaScript executes functions based on the LIFO (Last In, First Out) principle.
function foo() { console.log("Foo"); } function bar() { foo(); } bar();
Execution flow:
bar()
is pushed onto the Call Stack.bar()
callsfoo()
, sofoo()
is pushed onto the Call Stack.- Once
foo()
completes, it is popped off the Call Stack. - Once
bar()
completes, it is popped off the Call Stack.
Web APIs
Functions like setTimeout
, fetch
, and event listeners don't run in the Call Stack; they are handed off to Web APIs.
console.log("Start");
setTimeout(() => console.log("Timeout"), 0);
console.log("End");
Output:
Start
End
Timeout
Even though setTimeout
has a 0ms delay, it doesn’t run immediately but is handed off to the Web APIs and then the Task Queue.
Task Queue (Macro-task Queue)
The Task Queue contains macro-tasks such as:
setTimeout
setInterval
setImmediate
(Node.js)- I/O Callbacks
The Event Loop only processes one task from the Task Queue at a time and pushes it into the Call Stack if the Call Stack is empty.
Microtask Queue (Priority Queue)
The Microtask Queue has higher priority than the Task Queue and contains:
- Promises (resolve, reject)
- MutationObserver
- queueMicrotask
console.log("Start");
setTimeout(() => console.log("Timeout"), 0);
Promise.resolve().then(() => console.log("Promise"));
console.log("End");
Output:
Start
End
Promise
Timeout
Microtasks (Promise.resolve()
) always run before Tasks (setTimeout
).
3. How the Event Loop Works
- Execute the Call Stack (synchronous code).
- Process all microtasks (Promise, queueMicrotask, MutationObserver).
- Process one task from the Task Queue (setTimeout, I/O, events).
- Repeat the process.
The Event Loop continues running until there is no more code to process.
4. Async/Await and the Event Loop
async/await
makes asynchronous code more readable but still follows the Event Loop mechanism.
async function fetchData() {
console.log("Start Fetch");
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("End Fetch");
}
console.log("Before Fetch");
fetchData();
console.log("After Fetch");
Output:
Before Fetch
Start Fetch
After Fetch
End Fetch
Explanation:
fetchData()
callsconsole.log("Start Fetch")
→ Call Stack.await
pausesfetchData()
, handing control back to the Event Loop.console.log("After Fetch")
runs.- After 1 second,
console.log("End Fetch")
runs.
5. Summary
✅ JavaScript is single-threaded but can handle asynchronous tasks with the Event Loop.
✅ Microtasks (Promises) always run before Macro-tasks (setTimeout, I/O).
✅ Async/Await makes asynchronous code more readable but does not change the Event Loop mechanism.
Understanding the Event Loop helps you optimize JavaScript application performance and avoid issues related to asynchronous code! 🚀