Khi bạn viết một ứng dụng JavaScript, đặc biệt là trong trình duyệt hoặc Node.js, việc xử lý bất đồng bộ là một thách thức lớn. Event Loop chính là trái tim của cách JavaScript xử lý bất đồng bộ, giúp bạn hiểu được tại sao ngôn ngữ này có thể hoạt động đồng thời mà chỉ có một luồng duy nhất.
Trong bài viết này, chúng ta sẽ tìm hiểu chi tiết về Event Loop, cách nó hoạt động, và tại sao nó quá quan trọng đối với JavaScript.
Danh mục bài viết
Toggle1.Event Loop là gì?
Event Loop là một cơ chế trong JavaScript để xử lý các sự kiện, thực thi code, thu thập kết quả từ các operations bất đồng bộ (như API call, timeout), và chạy các tasks còn lại trong hàng đợi (queue).
Tất cả những gì JavaScript thực hiện đều diễn ra trong một luồng duy nhất (single-threaded). Vì vậy, Event Loop giúp xử lý nhiều tác vụ một cách đồng thời mà không chọn luồng khác.

Nguồn : frontendlead.com
2.Cách Event Loop hoạt động
2.1 Call Stack (Ngăn xếp lời gọi)
Call Stack chứa danh sách các hàm đang chờ được thực thi. Khi bạn gọi một hàm, nó sẽ được đẩy vào Call Stack. Khi hàm kết thúc, nó sẽ được lấy ra khỏi Stack.
function sayHello() {
console.log('Hello!');
}
sayHello(); // Đẩy hàm sayHello() vào Call Stack, thực thi, sau đó lấy ra.
2.2 Web APIs
Trong môi trường trình duyệt, các hàm như setTimeout
, fetch
, hay DOM events được xử lý bởi Web APIs. Nó cung cấp các công cụ để xử lý các tác vụ bất đồng bộ.
console.log('Start');
setTimeout(() => {
console.log('Timeout');
}, 1000);
console.log('End');
Start
End
Timeout
2.3 Task Queue (Hàng đợi tác vụ)
Task Queue là nơi lưu trữ các tác vụ bất đồng bộ như callback của setTimeout
, setInterval
, hoặc các sự kiện DOM. Các tác vụ trong Task Queue sẽ chờ cho đến khi Call Stack trống trước khi được đưa vào thực thi.
- Khi Call Stack trống, Event Loop sẽ kiểm tra Task Queue.
- Nếu có tác vụ trong Task Queue, nó sẽ được đẩy vào Call Stack để thực thi.
Ví dụ:
console.log('Start');
setTimeout(() => {
console.log('Task from Task Queue');
}, 0);
console.log('End');
Kết quả:
Start
End
Task from Task Queue
2.4 Microtask Queue (Hàng đợi vi tác vụ)
Microtask Queue chứa các tác vụ có độ ưu tiên cao hơn Task Queue, ví dụ như Promise
hoặc MutationObserver
. Các tác vụ trong Microtask Queue sẽ được thực thi ngay sau khi Call Stack trống, trước khi đến các tác vụ trong Task Queue.
Ví dụ:
console.log('Start');
Promise.resolve().then(() => {
console.log('Microtask');
});
setTimeout(() => {
console.log('Macrotask');
}, 0);
console.log('End');
Kết quả:
Start
End
Microtask
Macrotask
2.5 Event Loop
Event Loop là cơ chế kiểm tra liên tục:
- Nếu Call Stack trống, Event Loop sẽ lấy các tác vụ từ Microtask Queue trước, sau đó mới đến Task Queue.
- Quá trình này lặp lại cho đến khi tất cả tác vụ được xử lý.
3.Microtask vs Macrotask
3.1 Microtask
- Định nghĩa: Các tác vụ ưu tiên cao, được thực thi ngay sau khi Call Stack trống.
- Ví dụ: Promise.then, queueMicrotask, MutationObserver.
- Thứ tự thực thi: Được thực hiện trước Macrotask.
3.2 Macrotask
- Định nghĩa: Các tác vụ có độ ưu tiên thấp hơn, thường được đưa vào Task Queue.
- Ví dụ: setTimeout, setInterval, I/O events.
- Thứ tự thực thi: Được thực hiện sau khi tất cả Microtask đã hoàn tất.
Ví dụ:
console.log('Start');
setTimeout(() => {
console.log('Macrotask');
}, 0);
Promise.resolve().then(() => {
console.log('Microtask');
});
console.log('End');
Kết quả:
Start
End
Microtask
Macrotask
4.Ứng dụng thực tế
Hiểu cách hoạt động của Event Loop là rất quan trọng để tránh các lỗi không mong muốn trong ứng dụng của bạn. Dưới đây là một vài tình huống phổ biến:
4.1. Tối ưu hiệu suất
Khi thực hiện các tác vụ nặng trong JavaScript, bạn có thể chia nhỏ chúng để tránh việc chặn Call Stack, giúp giao diện không bị “đơ”:
const heavyTask = () => {
for (let i = 0; i < 1e6; i++) {
// Một tác vụ nặng
}
console.log('Task Done');
};
setTimeout(() => console.log('Timeout'), 0);
heavyTask();
Kết quả:
Task Done
Timeout
Bằng cách sử dụng setTimeout
hoặc Promise
, bạn có thể cải thiện hiệu suất bằng cách chia nhỏ tác vụ:
const heavyTask = () => {
for (let i = 0; i < 1e6; i++) {
if (i % 1e5 === 0) {
console.log(i);
}
}
};
setTimeout(() => console.log('Timeout'), 0);
Promise.resolve().then(() => heavyTask());
4.2. Tránh các lỗi khó hiểu
Một số lỗi có thể xảy ra khi bạn không hiểu rõ thứ tự thực thi:
setTimeout(() => console.log('setTimeout'), 0);
Promise.resolve().then(() => console.log('Promise'));
console.log('Sync code');
Kết quả:
Sync code
Promise
setTimeout
5.Kết luận
Event Loop là một trong những khái niệm cốt lõi mà mọi lập trình viên JavaScript nên hiểu rõ. Việc nắm vững cách Event Loop hoạt động không chỉ giúp bạn viết code hiệu quả hơn mà còn tránh được các lỗi phức tạp khi làm việc với các tác vụ bất đồng bộ. Hãy dành thời gian thực hành và kiểm tra kết quả để hiểu sâu hơn về cơ chế này.
Ở trên là 1 số chia sẻ về event loop trong javascript của mình, vì đây đa phần là kiến thức mình tự học nên có thể bài viết này có thể có nhiều chỗ chưa chính xác và thiếu sót, mong mọi người góp ý ở dưới phần bình luận để bài viết thêm hoàn chỉnh và xây dựng một cộng đồng nơi mọi người có thể chia sẻ kinh nghiệm lập trình cho nhau 😘.