1. Event Loop là gì
Event Loop là một cơ chế cho phép xử lý các tác vụ bất đồng bộ trong JavaScript, sử dụng một vòng lặp vô tận để kiểm tra, quản lý các tác vụ. JavaScript có thể xử lý nhiều tác vụ cùng lúc mặc dù JavaScript là single-threaded - tại một thời điểm chỉ thực hiện một tác vụ duy nhất.
2. Tại sao Event Loop lại quan trọng?
- Tránh tình trạng ứng dụng bị treo : Xử lý các tác vụ bất đồng bộ mà không làm nghẽn luồng chính.
- Xử lý bất đồng bộ: Xử lý tác vụ bất đồng bộ như là call API hay thao tác với cơ sở dữ liệu mà không làm chậm chương trình.
- An toàn khi thực thi tác vụ bất đồng bộ: Event Loop quản lý và điều phối quá trình thực thi các tác vụ không bị xung đột do việc chia sẻ tài nguyên trong Javascript.
- Hỗ trợ Promises và async/await: Thay vì phải dùng nhiều callback lồng nhau thì chỉ cần async/await để đoạn mã trở nên đơn giản, dễ quản lý hơn.
- Kiểm soát quá trình thực thi: Hiểu sâu về Event Loop, giúp các bạn dev dự đoán được luồng thực thi, debug hiệu quả, nhanh chóng.
- Giao diện người dùng: Duy trì trải nghiệm cho người dùng khi có các tác vụ nặng.
- Nâng cao hiệu suất: Javascript có thể xử lý nhiều tác vụ hiệu quả hơn mà không cần phải tạo nhiều luồng, tiết kiệm tài nguyên của hệ thống.
3. Cơ chế hoạt động Event Loop trong JS
3.1 Call Stack
- Call stack là một thành phần quan trọng trong JS, quản lý việc thực thi tác vụ của chương trình.
- Khi một hàm được gọi trong JavaScript, hàm đó sẽ được thêm vào call stack.
- Call stack hoạt động theo cơ chế FILO (First In Last Out).
- Khi hàm được thực hiện sẽ được đẩy ra khỏi call stack.
JavaScript chỉ có thể xử lý một tác vụ tại một thời điểm. Điều này có nghĩa là các tác vụ chạy lâu hơn có thể sẽ chặn bất kỳ tác vụ khác thực thi phía sau, dễ hiểu hơn là đóng băng chương trình!
Trong ví dụ trên, hàm importantTask() phải đợi cho đến khi longRunningTask() được đẩy ra khỏi Call Stack, việc này sẽ mất một chút thời gian.
Trong thực tế khi xây dựng chương trình sẽ có nhiều tác vụ chạy lâu hơn. Điều này có khiến cho toàn bộ ứng dụng treo không?
May mắn, câu trả lời là không! Chức năng này thực ra không phải là một phần của JavaScript; Browser hỗ trợ WebAPIs
3.2 Web APIs
Web API cung cấp bộ giao diện tương tác bao gồm các tính năng như: fetch, setTimeout và một vài tính năng khác.
Web API cho phép xử lý các tác vụ không đồng bộ và giảm tải các longer-running task cho trình duyệt. Việc gọi một phương thức API thực chất chỉ là chuyển longer-running task sang môi trường trình duyệt và thiết lập để xử lý tác vụ này khi nó hoàn thành.
Sau khi bắt đầu asyncTask (không cần đợi kết quả), task sẽ nhanh chóng bị đẩy ra khỏi Call Stack.
Web API hỗ trợ bất đồng bộ thường dùng hai cách: callback-based hoặc promise-based.
Mình có một ví dụ mong muốn lấy vị trí hiện tại của người dùng trên website.
Để làm được điều này, chúng ta có thể sử dụng getCurrentPosition method nhận hai callbacks: successCallback sẽ được sử dụng khi lấy vị trí thành công và errorCallback được sử dụng khi có bất kỳ lỗi.
Hàm cần xử lý sẽ được đẩy vào call stack và trình duyệt sẽ xử lý các thao tác cần thiết. Sau đó hàm này được đẩy ra khỏi call stack. Bây giờ trình duyệt sẽ chịu trách nhiệm.
Sau đó, browser sẽ yêu cầu người dùng cấp cho quyền truy cập vào vị trí của họ. Mình không thể chắc chắn được rằng khi nào người dùng sẽ tương tác với yêu cầu truy cập đó.
Nhưng điều đó không phải là vấn đề. Tất cả được web APIs hỗ trợ và call stack vẫn có thể đảm nhận và thực hiện các tác vụ khác.
Sau khi người dùng cho phép truy cập vào vị trí hiện tại của họ. API sẽ nhận dữ liệu từ Browser và sử dụng successCallback để xử lý kết quả.
Các bạn có thắc mắc successCallback sẽ được đưa vào đâu để thực thi không?
Không đơn giản sẽ được đẩy ngược lại call stack bởi vì làm như vậy có thể gián đoạn một tác vụ nào đó đang chạy. Điều này có thể dẫn đến trường hợp conflicts.
3.3 Task Queue
Thay vào đó, successCallback sẽ được thêm vào Task Queue (hay còn gọi là Callback Queue).
Task Queue chứa các Web API callbacks và các sự kiện đang chờ thực thi tại thời điểm nào đó trong tương lai.
Bây giờ thì, successCallback đang ở trong Task Queue … Nhưng mà khi nào thì sẽ được thực thi?
3.4 Event Loop
Đây chính là trách nhiệm của Event Loop, nó có nhiệm vụ liên tục kiểm tra xem call stack có trống không.
Bất cứ khi nào call stack trống - nghĩa là không có bất kỳ tác vụ nào đang được thực thi - nó nhận task đầu tiên từ task queue và chuyển lên call stack, tại đây callback sẽ được thực thi.
Một callback-based Web APIs phổ biến khác là setTimeout. Bất cứ khi nào các bạn gọi setTimeout, sẽ được thực thi trong call stack - chỉ chịu trách nhiệm khởi tạo bộ đếm thời gian, browser sẽ theo dõi bộ đếm thời gian.
Khi bộ đếm thời gian hết hạn, timer’s callback sẽ được đưa vào task queue. Nếu như call stack đang bận xử lý các tasks khác, thì callback sẽ phải đợi trong Task queue.
Đến đây, bạn cũng đã hiểu callback-based APIs được xử lý như thế nào. Tuy nhiên, hầu hết Web APIs sử dụng promise-based. Chúng sẽ xử lý khác nhau như thế nào?
3.5 Microtask Queue
Microtask Queue là một hàng đợi khác trong runtime với độ ưu tiên cao hơn Task Queue. Hàng đợi này dành riêng cho:
- Promise handler callbacks (then(callback), catch(callback) và finally(callback))
- async/await
- MutationObserver callbacks
- queueMicrotask callbacks
Khi Call Stack trống, Event Loop sẽ ưu tiên các tác vụ ở Microtask Queue trước Task Queue
Khi các bạn đọc đến đây, mình nghĩ các bạn sẽ thắc mắc: “Liệu có phải tất cả Web APIs được xử lý bất đồng bộ không?”
Không nhé! Các method khác ví dụ như localStorage.setItem(), document.getElementById() vẫn được xử lý đồng bộ.
4. Demo Event Loop với Javascript
Promise.resolve()
.then(() => {
console.log('1');
});
setTimeout(() => {
console.log('2');
}, 10);
queueMicrotask(() => {
console.log('3');
queueMicrotask(() => {
console.log('4');
});
});
console.log("5");
Để tổng hợp lại tất cả kiến thức các bạn hãy thử dự đoán kết quả hiển thị đoạn mã Javascript phía trên nhé! Trước khi xem phần kết quả.
Kết quả hiển thị
5 1 3 4 2
5. Kết Luận
Sau bài viết này bạn sẽ:
- Hiểu rõ JavaScript asynchronous và non-blocking, biết cách Event Loop, Task Queue, và Microtask Queue làm việc cùng nhau.
- Event Loop quản lý thứ tự thực hiện các công việc, ưu tiên Microtask Queue trước để đảm bảo các promises và các tác vụ liên quan được xử lý nhanh chóng, sau đó mới đến các công việc trong Task Queue.
- Cách hoạt động này giúp cho JavaScript xử lý các thao tác không đồng bộ phức tạp trong môi trường đơn luồng.
Bạn hãy thường xuyên theo dõi các bài viết hay về Lập Trình & Dữ Liệu trên 200Lab Blog nhé. Cũng đừng bỏ qua những khoá học Lập Trình tuyệt vời trên 200Lab nè.
Bài viết liên quan
NodeJS là gì? Tại sao NodeJS lại phổ biến
Jul 09, 2024 • 8 min read
So sánh Golang và NodeJS chi tiết
Oct 14, 2023 • 22 min read
Home Feed UI Instagram with TailwindCSS: Giới thiệu về series
Nov 28, 2022 • 2 min read
ReactJS Tutorial for Beginners Phần 2
Dec 12, 2021 • 3 min read
ReactJS vs React Native - Gà cùng một mẹ liệu có giống nhau?
Dec 12, 2021 • 14 min read
Netflix Clone với ReactJS, Styled Components và Firebase (Firestore & Auth) - Phần 7
Dec 08, 2021 • 8 min read