JavaScript không biến thành multi-threaded chỉ vì có async. Trên browser, event loop phối hợp call stack, task queues và Web APIs để callback tiếp tục chạy khi công việc bất đồng bộ hoàn tất.
Callback
Callback là function được truyền để gọi sau:
1 | function loadUser(id, callback) { |
Node-style callback thường dùng (error, value). Callback không mặc định bất đồng bộ; Array#map cũng nhận callback nhưng chạy đồng bộ.
Nhiều tầng callback làm control flow và error handling khó đọc, nhưng vấn đề không phải callback “xấu”; vấn đề là composition yếu.
Promise
Promise đại diện cho kết quả sẽ fulfilled hoặc rejected:
1 | function loadUser(id) { |
Luôn return promise trong .then; nếu quên, chain tiếp theo không chờ công việc đó.
async/await
async function luôn trả Promise. await tạm dừng function đó, không block toàn bộ thread:
1 | async function renderUserOrders(userId) { |
async/await là syntax trên Promise, không phải concurrency engine mới.
Chạy tuần tự hay song song
1 | // Tuần tự |
Request thứ hai phụ thuộc request đầu nên tuần tự là đúng.
1 | // Song song |
Dùng Promise.allSettled khi muốn thu kết quả từng task dù một task fail.
Microtask và task
Promise reactions chạy trong microtask queue, thường được drain sau stack hiện tại và trước task tiếp theo như timer:
1 | console.log('A'); |
Đây là thứ tự queue, không phải Promise “nhanh hơn” timer theo nghĩa performance.
Hủy và timeout
Promise tự nó không có nút cancel chung. API như Fetch dùng AbortController:
1 | const controller = new AbortController(); |
Quy tắc thực dụng
- Dùng callback cho event/subscription.
- Dùng Promise cho một kết quả bất đồng bộ.
- Dùng
async/awaitđể control flow dễ đọc. - Chạy song song chỉ khi task độc lập.
- Luôn xử lý rejection và cleanup.
Syntax có thể đẹp hơn, nhưng lỗi race condition vẫn xấu như cũ. Nó chỉ mặc áo mới.