Từ setTimeout(..., 0) đến Event Loop: Bài học từ một lỗi hiểu nhầm phổ biến

Từ setTimeout(..., 0) đến Event Loop: Bài học từ một lỗi hiểu nhầm phổ biến

Xem nhanh

✨ Ủa?

Hồi trước mình nhớ đang loay hoay tìm cách fix cho một vấn đề thực hiện Action A, khi UI render hoàn tất.

Mình tìm kiếm giải pháp trên StackOverflow thì thấy 1 cách ngắn gọn như thế này:

Copy
setTimeout(() => { console.log("Action A") }, 0);

Lúc đó mình chỉ đơn giản apply mà chẳng chút mảy may RUN FIRST =]]

Sau đó một khoảng mình đọc lại code, mình thắc mắc:

Ủa sao chờ 0 giây thì chờ làm gì?

Ủa rồi nếu là 0 giây thì nó phải chạy liền chớ? Sao có thể fix được vấn đề trước đó của mình 😵‍💫

✨ Một ví dụ đơn giản bóc mẽ cú lừa của setTimeout(…, 0)

Hồi đó mình ngây thơ nghĩ rằng nếu viết code theo thứ tự từ trên xuống dưới thì nó cũng sẽ chạy đúng thứ tự đó.

Thế nên khi mình thử đoạn code sau:

Copy
setTimeout(() => console.log("Action 1"), 0);
console.log("Action 2");

Mình đã kỳ vọng kết quả sẽ là:

Copy
Action 1  
Action 2

Vì đơn giản thôi mà: setTimeout(...) viết trước, thì nó phải chạy trước, đúng không?

❌ Nhưng sự thật phủ phàng là:

Copy
Action 2  
Action 1

Lúc này mình như kiểu:

Ủa?? JavaScript có đọc từ trên xuống dưới không vậy?

Và rồi mình nhận ra...

Mình chưa hiểu Event Loop! 😭

✨ Event Loop – Đơn luồng nhưng không đơn giản

Đây chính là thứ khiến JavaScript trở nên đặc biệt:

"Đơn luồng nhưng không đơn giản!"

💡 JavaScript hoạt động như thế nào?

1. Call Stack

Là nơi mọi hàm được gọi và thực thi. JavaScript chạy code theo thứ tự từ trên xuống dưới, từng hàm một sẽ được "xếp chồng" vào đây. Xử lý xong hàm nào thì “gỡ” hàm đó ra.

2. Task Queue

Là hàng chờ đứng ngoài cổng Call Stack. Những hàm không chạy ngay – như setTimeout, setInterval, hoặc các callback từ sự kiện – sẽ được xếp vào đây để chờ tới lượt.

3. Event Loop

Là “ông kiểm soát” đứng giữa, canh xem Call Stack có trống không. Nếu trống, ông mới cho các task từ Task Queue nhảy vào chạy tiếp.

🧪 Áp dụng vào ví dụ:

Copy
setTimeout(() => console.log("Action 1"), 0);
console.log("Action 2");

🕵️ Diễn biến:

  • setTimeout(..., 0) được gửi sang Task Queue với lời hứa:

Cho tao chạy sau, khi mày rảnh!

  • console.log("Action 2") nằm trong Call Stack nên được chạy ngay.
  • Khi Call Stack trống, Event Loop mới lấy Action 1 từ Task Queue để chạy.

👉 Kết quả in ra sẽ là:

Copy
Action 2  
Action 1

✨ Lời kết

  • setTimeout(fn, 0) KHÔNG BAO GIỜ có nghĩa là “chạy ngay lập tức”. Mà nghĩa là:

Đặt fn vào hàng chờ và chờ ít nhất 0ms, sau khi code đồng bộ chạy xong.

=> Hay nói cách khác, nó chỉ giúp delay fn sang vòng event loop tiếp theo.

  • Event loop là một khái niệm quan trọng trong lập trình JS để có thể nắm được cách thức thực thi của mã JS.

Giờ thì mình đã không còn thắc mắc nữa :))

✨ Tài liệu tham khảo

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model