Event Loop Node.js: 'Trái Tim' Xử Lý Bất Đồng Bộ Của Bạn
Nodejs

Event Loop Node.js: 'Trái Tim' Xử Lý Bất Đồng Bộ Của Bạn

Author

Admin System

@root

Ngày xuất bản

18 Mar, 2026

Lượt xem

3 Lượt

"Event Loop"

Event Loop trong Node.js: 'Trái Tim' Xử Lý Bất Đồng Bộ Của Bạn

Chào các coder Gen Z! Hôm nay chúng ta sẽ "mổ xẻ" một trong những khái niệm "hack não" nhất nhưng cũng "quyền năng" nhất trong Node.js: Event Loop. Nghe tên thì có vẻ phức tạp như một dự án nghiên cứu vũ trụ, nhưng thực ra nó chỉ là "anh shipper" siêu tốc giúp app của bạn không bị "đơ" khi phải làm nhiều việc cùng lúc.

1. Event Loop là gì và để làm gì?

Imagine bạn là một barista siêu sao trong quán cà phê "Node.js" cực kỳ đông khách. Bạn chỉ có một mình (JavaScript là đơn luồng - single-threaded), nhưng phải xử lý hàng tá order cùng lúc: nào là cà phê đá, nào là trà sữa trân châu, nào là bánh ngọt, lại còn phải thu tiền và lau bàn nữa. Nếu bạn làm từng việc một, khách hàng sẽ "bùng kèo" hết vì chờ lâu.

Event Loop chính là "hệ thống quản lý order thông minh" của bạn. Nó không cho phép bạn bị mắc kẹt vào một order nào đó quá lâu. Thay vào đó, khi có một order cần thời gian (ví dụ: pha cà phê cần máy xay, máy pha tự động), bạn sẽ ghi order đó vào "phiếu chờ" (Callback Queue) và chuyển sang làm việc khác ngay lập lập tức. Khi máy pha cà phê xong, hoặc có khách mới đến, "anh shipper" Event Loop sẽ "nhặt" order đã hoàn thành từ "phiếu chờ" và đưa vào "khu vực làm việc chính" (Call Stack) để bạn xử lý nốt.

Nói một cách "hàn lâm" hơn:

  • JavaScript là đơn luồng: Điều này có nghĩa là tại một thời điểm, nó chỉ có thể thực thi một đoạn mã duy nhất.
  • Vấn đề: Nếu một tác vụ tốn thời gian (ví dụ: đọc file từ ổ cứng, gọi API mạng) được thực thi đồng bộ, toàn bộ ứng dụng sẽ bị "treo" (blocking) cho đến khi tác vụ đó hoàn thành.
  • Giải pháp: Event Loop: Node.js (dựa trên engine V8 của Chrome) sử dụng Event Loop để xử lý các tác vụ bất đồng bộ (asynchronous) mà không chặn luồng chính. Nó hoạt động như một cơ chế liên tục kiểm tra xem Call Stack có rỗng không và nếu có, nó sẽ đẩy các hàm callback từ Callback Queue (hay Task Queue) vào Call Stack để thực thi.

Các thành phần chính tham gia vào "vở kịch" Event Loop bao gồm:

  • Call Stack: Nơi các hàm đang được thực thi được đặt vào. Khi một hàm kết thúc, nó sẽ bị pop ra khỏi stack.
  • Heap: Vùng bộ nhớ để lưu trữ các đối tượng và biến.
  • Node.js C++ APIs (Web APIs): Các API cấp thấp được Node.js cung cấp (ví dụ: fs.readFile, http.request, setTimeout, setImmediate). Khi bạn gọi các hàm bất đồng bộ này, chúng sẽ được Node.js chuyển giao cho các luồng Worker Pool bên dưới (hoặc các cơ chế khác) để xử lý, không làm chặn Call Stack.
  • Callback Queue (Task Queue/MacroTask Queue): Nơi các hàm callback từ các tác vụ bất đồng bộ (như setTimeout, setImmediate, I/O) được xếp hàng chờ đợi để được đưa vào Call Stack.
  • MicroTask Queue: Một hàng đợi có độ ưu tiên cao hơn Callback Queue. Chứa các callback từ Promise.then(), process.nextTick(), queueMicrotask(). Các microtask luôn được ưu tiên thực thi hết trước khi Event Loop chuyển sang phase tiếp theo hoặc xử lý macro task.
Illustration

2. Code Ví Dụ Minh Họa: Ai Chạy Trước, Ai Chạy Sau?

Để hiểu rõ hơn về các pha của Event Loop trong Node.js (timers, pending callbacks, idle/prepare, poll, check, close callbacks) và sự khác biệt giữa setTimeout, setImmediate, process.nextTick và Promises, hãy xem ví dụ này:

console.log('1. Start');

// Microtask 1: Highest priority, runs before next tick
process.nextTick(() => {
  console.log('2. process.nextTick callback');
});

// Microtask 2: Promise, runs after process.nextTick but before macrotasks
Promise.resolve().then(() => {
  console.log('3. Promise.then callback');
});

// Macrotask 1: Timer phase
setTimeout(() => {
  console.log('4. setTimeout callback (0ms)');
  process.nextTick(() => {
    console.log('5. process.nextTick inside setTimeout');
  });
  Promise.resolve().then(() => {
    console.log('6. Promise.then inside setTimeout');
  });
}, 0);

// Macrotask 2: Check phase
setImmediate(() => {
  console.log('7. setImmediate callback');
  process.nextTick(() => {
    console.log('8. process.nextTick inside setImmediate');
  });
  Promise.resolve().then(() => {
    console.log('9. Promise.then inside setImmediate');
  });
});

// Macrotask 3: Timer phase (another setTimeout)
setTimeout(() => {
  console.log('10. Another setTimeout callback (0ms)');
}, 0);

console.log('11. End (Synchronous code)');

Output dự kiến:

1. Start
11. End (Synchronous code)
2. process.nextTick callback
3. Promise.then callback
4. setTimeout callback (0ms)
5. process.nextTick inside setTimeout
6. Promise.then inside setTimeout
10. Another setTimeout callback (0ms)
7. setImmediate callback
8. process.nextTick inside setImmediate
9. Promise.then inside setImmediate

Giải thích:

  1. 1. Start11. End chạy trước vì chúng là mã đồng bộ.
  2. Sau khi Call Stack rỗng, Event Loop kiểm tra MicroTask Queue. process.nextTick có ưu tiên cao nhất, nên 2. process.nextTick callback chạy.
  3. Tiếp theo là các Promise microtask, nên 3. Promise.then callback chạy.
  4. Bây giờ, Event Loop chuyển sang các pha khác. Nó vào pha timers, thực thi setTimeout đầu tiên: 4. setTimeout callback (0ms).
  5. QUAN TRỌNG: Khi một callback được thực thi (ví dụ setTimeout), nó có thể tạo ra các microtask mới. Các microtask này sẽ được thực thi ngay lập tức sau khi callback hiện tại kết thúc, trước khi Event Loop chuyển sang macro task tiếp theo hoặc pha tiếp theo. Do đó, 5. process.nextTick inside setTimeout6. Promise.then inside setTimeout chạy ngay sau 4.
  6. Event Loop tiếp tục trong pha timers và thực thi setTimeout thứ hai: 10. Another setTimeout callback (0ms).
  7. Sau khi hoàn thành pha timers, Event Loop chuyển sang pha check và thực thi setImmediate: 7. setImmediate callback.
  8. Tương tự như setTimeout, các microtask bên trong setImmediate sẽ chạy ngay lập tức: 8. process.nextTick inside setImmediate9. Promise.then inside setImmediate.

3. Mẹo (Best Practices) để "Thuần Phục" Event Loop

  • Đừng chặn Event Loop (Don't Block the Event Loop): Đây là "luật vàng"! Bất kỳ tác vụ đồng bộ nào chạy quá lâu (ví dụ: vòng lặp for chạy hàng triệu lần, tính toán phức tạp) sẽ làm "treo" toàn bộ ứng dụng của bạn. Hãy offload chúng bằng cách sử dụng các hàm bất đồng bộ, Worker Threads, hoặc chia nhỏ tác vụ.
  • Hiểu rõ process.nextTick vs. setImmediate vs. setTimeout:
    • process.nextTick(): Chạy ngay lập tức sau mã đồng bộ hiện tại và trước bất kỳ I/O hoặc timer nào khác. Ưu tiên cao nhất trong MicroTask Queue.
    • Promise.then(): Cũng là microtask, chạy sau process.nextTick nhưng trước các macrotask.
    • setTimeout(fn, 0): Đặt fn vào hàng đợi timers để chạy trong pha timers tiếp theo. Thời gian 0ms chỉ có nghĩa là nó sẽ được chạy càng sớm càng tốt sau khi timer hết hạn, không có nghĩa là chạy ngay lập tức.
    • setImmediate(fn): Đặt fn vào hàng đợi check và sẽ chạy trong pha check tiếp theo của Event Loop. Thường chạy sau setTimeout(fn, 0) trong hầu hết các trường hợp, đặc biệt là khi không có I/O.
  • Sử dụng async/await: Để viết code bất đồng bộ trông giống như đồng bộ, dễ đọc và dễ quản lý hơn, tránh "callback hell". async/await thực chất là "đường cú pháp" (syntactic sugar) cho Promises.
  • Worker Threads cho tác vụ nặng: Nếu bạn có các tác vụ tính toán cực kỳ nặng mà không thể làm bất đồng bộ (ví dụ: xử lý hình ảnh phức tạp, mã hóa dữ liệu), hãy sử dụng Node.js Worker Threads để chạy chúng trên một luồng riêng biệt, không làm chặn Event Loop chính.

4. Ứng Dụng Thực Tế

Event Loop là lý do Node.js trở thành "ông trùm" trong các ứng dụng cần xử lý nhiều kết nối đồng thời mà vẫn duy trì hiệu suất cao. Bạn thấy nó ở khắp mọi nơi:

  • Chat Applications (ứng dụng chat): Như Slack, Discord, Messenger. Hàng ngàn người dùng gửi và nhận tin nhắn liên tục. Event Loop giúp server Node.js quản lý tất cả các kết nối này mà không bị "nghẽn" một giây nào.
  • API Servers (máy chủ API): Các backend phục vụ hàng triệu yêu cầu từ ứng dụng di động hoặc web. Node.js xử lý các yêu cầu cơ sở dữ liệu, gọi API bên ngoài (microservices) một cách bất đồng bộ, giúp phản hồi nhanh chóng.
  • Real-time Data Streaming: Các dịch vụ truyền tải dữ liệu theo thời gian thực như cập nhật giá chứng khoán, thông báo thể thao. Event Loop cho phép Node.js lắng nghe và đẩy dữ liệu liên tục mà không làm chậm hệ thống.
  • IoT Backends: Xử lý dữ liệu từ hàng ngàn thiết bị IoT (Internet of Things) gửi dữ liệu liên tục. Node.js với Event Loop là lựa chọn lý tưởng cho các gateway và backend IoT.

Hiểu được Event Loop không chỉ giúp bạn viết code Node.js hiệu quả hơn mà còn giúp bạn "gỡ lỗi" những vấn đề khó hiểu về thứ tự thực thi. Hãy coi nó như "người bạn thân" của bạn trong thế giới lập trình bất đồng bộ nhé!

Thuộc Series: Nodejs

Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!

#tech #cyberpunk #laravel
Chỉnh sửa bài viết

Bình luận (0)

Vui lòng Đăng Nhập để Bình luận

Hỗ trợ Markdown cơ bản
Nguyễn Văn A
1 ngày trước

Tính năng này đỉnh quá ad ơi, chờ mãi mới thấy một blog Tiếng Việt có UI/UX xịn như vầy!