Explore all courses in this category.
Explore all courses in this category.
Nguyễn Đình Cường
0 comment
09 Nov, 2026
Hầu như mọi thao tác trong backend Node.js đều bất đồng bộ: gọi database, đọc file, gọi API. Async/await giúp code đọc như tuần tự nhưng vẫn không chặn luồng. Tuy nhiên dùng sai cách, bạn sẽ gặp lỗi khó thấy hoặc làm chương trình chậm không cần thiết.
Một hàm async luôn trả về Promise. Từ khóa await tạm dừng hàm đó cho đến khi Promise xong, nhưng không chặn cả chương trình. Nó chỉ dùng được trong hàm async (hoặc ở cấp cao nhất của module ES).
async function getUserWithPosts(id) { const user = await db.users.findById(id); const posts = await db.posts.findByUser(id); return { ...user, posts };}Trong ví dụ trên, hai truy vấn độc lập nhưng bị chạy nối tiếp, tổng thời gian bằng tổng hai truy vấn. Nếu chúng không phụ thuộc nhau, hãy chạy song song:
async function getUserWithPosts(id) { const [user, posts] = await Promise.all([ db.users.findById(id), db.posts.findByUser(id), ]); return { ...user, posts };}Lưu ý Promise.all thất bại ngay khi một Promise bị từ chối. Nếu muốn lấy kết quả của tất cả dù có cái lỗi, dùng Promise.allSettled.
Một lỗi hiệu năng thường gặp là await từng phần tử trong for khi các việc độc lập:
// Chậm: lần lượt từng cáifor (const id of ids) { await sendEmail(id);}// Nhanh hơn: song songawait Promise.all(ids.map((id) => sendEmail(id)));Nhưng chạy song song hàng nghìn việc cùng lúc có thể làm quá tải database hoặc API bên ngoài. Khi đó hãy giới hạn mức đồng thời, ví dụ chia mảng thành từng lô nhỏ hoặc dùng thư viện như p-limit. Còn khi thứ tự quan trọng hoặc mỗi bước phụ thuộc bước trước, vòng lặp tuần tự lại là lựa chọn đúng.
async function loadConfig() { try { const raw = await fs.promises.readFile('config.json', 'utf8'); return JSON.parse(raw); } catch (err) { if (err.code === 'ENOENT') return {}; // file chưa có thì dùng mặc định throw err; // lỗi khác thì ném tiếp }}Chỉ bắt lỗi mà bạn thật sự xử lý được. Nuốt lỗi bằng catch rỗng là cách nhanh nhất để tạo ra bug bí ẩn. Với lỗi bạn không biết xử lý, hãy để nó nổi lên đến tầng có thể ghi log và phản hồi đúng cách.
Một lời gọi mạng treo có thể giữ tài nguyên mãi. Hãy đặt giới hạn thời gian:
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });if (!res.ok) throw new Error('HTTP ' + res.status);const data = await res.json();forEach với async: forEach không chờ các callback async; hãy dùng for...of hoặc Promise.all(map(...)).async vào hàm không có await chỉ thêm một lớp Promise vô ích.Ghép các mẫu trên, bạn có thể viết một hàm lấy dữ liệu từ dịch vụ ngoài, có thời hạn chờ và thử lại vài lần khi lỗi tạm thời:
async function fetchWithRetry(url, retries = 3) { for (let attempt = 1; attempt <= retries; attempt++) { try { const res = await fetch(url, { signal: AbortSignal.timeout(5000) }); if (!res.ok) throw new Error('HTTP ' + res.status); return await res.json(); } catch (err) { if (attempt === retries) throw err; await new Promise((r) => setTimeout(r, attempt * 500)); // chờ lâu dần } }}Hàm này tăng dần thời gian chờ giữa các lần thử, một kỹ thuật gọi là backoff, giúp không dồn dập gọi dịch vụ đang gặp sự cố. Hãy chỉ thử lại với lỗi tạm thời (mạng, 5xx), không thử lại với lỗi do chính request sai như 400 hay 404.
Hãy lấy một đoạn code trong dự án của bạn có nhiều lời gọi await liên tiếp, vẽ ra sơ đồ phụ thuộc giữa chúng và xem chỗ nào có thể chạy song song. Đo thời gian trước và sau khi đổi sang Promise.all. Con số cụ thể sẽ thuyết phục bạn hơn mọi lý thuyết, và bạn sẽ có thói quen tự hỏi "việc này có thực sự phải chờ việc kia không?" mỗi khi viết một dòng await.
Async/await làm code bất đồng bộ dễ đọc, nhưng bạn vẫn phải nghĩ đến thứ tự, song song và lỗi. Hãy rà lại code của mình: chỗ nào đang await nối tiếp những việc độc lập, chỗ nào đang nuốt lỗi? Sửa hai điểm này thường đã cải thiện đáng kể cả tốc độ lẫn độ tin cậy.
Nếu muốn luyện tập cùng bài tập thực tế, hãy ghé các khóa học tại codebem.com khi bạn sẵn sàng.
Nguyễn Đình Cường
0 comment