Tìm hiểu về Microtask và Macrotask trong JavaScript

Trong môi trường thực thi JavaScript, khái niệm Microtask (vi tác vụ) và Macrotask (đại tác vụ) đóng vai trò then chốt trong cơ chế Event Loop. Việc nắm vững hai loại tác vụ này giúp developer hiểu rõ hơn về cách JavaScript xử lý các thao tác bất đồng bộ.

Cơ chế Event Loop

JavaScript hoạt động theo mô hình đơn luồng (single-threaded), nghĩa là tại một thời điểm chỉ có một tác vụ được thực thi. Để giải quyết vấn đề này, JavaScript sử dụng cơ chế Event Loop để quản lý và sắp xếp thứ tự thực thi các tác vụ.

Tác vụ đồng bộ (Synchronous)
  • Được thực thi ngay lập tức trên main thread
  • Thực hiện tuần tự từ trên xuống dưới
  • Không cần chờ đợi bất kỳ tác vụ nào khác
Tác vụ bất đồng bộ (Asynchronous)
  • Không chặn main thread
  • Được đưa vào hàng đợi và chờ đến lượt thực thi
  • Khi main thread rảnh, tác vụ sẽ được lấy ra và xử lý

Các tác vụ bất đồng bộ được chia thành hai loại: Macrotask và Microtask.

Macrotask (Đại tác vụ)

Macrotask là những tác vụ bất đồng bộ cơ bản trong JavaScript, bao gồm:

  • setTimeoutsetInterval (bộ đếm thời gian)
  • setImmediate (dành riêng cho môi trường Node.js)
  • Các thao tác I/O
  • Rendering giao diện người dùng

Đặc điểm của Macrotask:

  • Mỗi macrotask tạo ra một execution context mới
  • Sau khi hoàn thành một macrotask, toàn bộ microtask queue sẽ được xử lý trước khi chuyển sang macrotask tiếp theo
  • Một macrotask đại diện cho một vòng hoàn thành của Event Loop

Microtask (Vi tác vụ)

Microtask là loại tác vụ bất đồng bộ đặc biệt, được thực thi ngay sau khi macrotask hiện tại kết thúc nhưng không tạo context thực thi mới. Các microtask phổ biến:

  • Callback của .then() trong Promise
  • MutationObserver (hỗ trợ trong một số trình duyệt)
  • process.nextTick (dành riêng cho môi trường Node.js)

Đặc điểm của Microtask:

  • Thực thi ngay sau khi tất cả code đồng bộ của macrotask hiện tại chạy xong
  • Chạy trước khi macrotask tiếp theo bắt đầu
  • Nếu trong microtask có tạo thêm microtask mới, chúng sẽ được thực thi liên tục cho đến khi hết

Ví dụ thực thi

Xét đoạn code JavaScript sau:

console.log('1. Bắt đầu chương trình');

setTimeout(() => {
  console.log('2. setTimeout callback');
}, 0);

Promise.resolve().then(() => {
  console.log('3. Promise callback lần 1');
}).then(() => {
  console.log('4. Promise callback lần 2');
});

console.log('5. Kết thúc chương trình');

setImmediate(() => {
  console.log('6. setImmediate callback');
});

Kết quả xuất ra console:

1. Bắt đầu chương trình
5. Kết thúc chương trình
3. Promise callback lần 1
4. Promise callback lần 2
2. setTimeout callback
6. setImmediate callback

Phân tích chi tiết:

  1. Tác vụ đồng bộ (1. Bắt đầu chương trình, 5. Kết thúc chương trình) - được thực thi trước tiên
  2. Microtask (3. Promise callback lần 1, 4. Promise callback lần 2) - chạy ngay sau khi code đồng bộ hoàn tất
  3. Macrotask (2. setTimeout callback, 6. setImmediate callback) - thực thi theo thứ tự được đưa vào hàng đợi

Kết luận

  • Macrotask: Tạo execution context mới, thực thi tuần tự, mỗi tác vụ đại diện cho một vòng Event Loop
  • Microtask: Thực thi ngay sau khi code đồng bộ của macrotask hiện tại kết thúc và trước khi macrotask tiếp theo bắt đầu

Việc hiểu rõ sự khác biệt giữa hai loại tác vụ này giúp lập trình viên kiểm soát tốt hơn luồng thực thi của ứng dụng JavaScript, đặc biệt quan trọng khi làm việc với các thư viện asynchronous như Promise, async/await hay các framework frontend.

Thẻ: JavaScript event-loop Async promises nodejs

Đăng vào ngày 1 tháng 7 lúc 06:09