Async/Await trong JavaScript: Xử lý bất đồng bộ đúng cách
Bạn gọi API, website hiển thị "Đang tải..." nhưng người dùng vẫn cuộn trang, nhấn nút được — không bị đơ. Đó là nhờ lập trình bất đồng bộ (asynchronous). Và Async/Await là cú pháp hiện đại nhất để viết code bất đồng bộ trong JavaScript.
Bất đồng bộ là gì? Tại sao cần?
Tưởng tượng đặt đồ ăn tại quán:
- Đồng bộ: Đứng chờ tại quầy bếp cho đến khi xong, không làm gì khác được
- Bất đồng bộ: Đặt món rồi về chỗ ngồi làm việc — bếp gọi thì lấy
JavaScript là single-threaded — chỉ làm một việc tại một thời điểm. Nếu một request API mất 2 giây và code chạy đồng bộ, toàn bộ trang sẽ đóng băng 2 giây. Bất đồng bộ giải quyết vấn đề này.
Từ Callback đến Async/Await
Callback — Cách cũ, dễ gây "địa ngục"
getData(function(a) {
getMoreData(a, function(b) {
getEvenMoreData(b, function(c) {
console.log(c);
// Ngày càng lún sâu — "callback hell"
});
});
});
Promise — Cải thiện đáng kể
fetch("https://api.example.com/data")
.then(response => response.json())
.then(data => fetch(`https://api.example.com/detail/${data.id}`))
.then(detail => detail.json())
.then(detailData => console.log(detailData))
.catch(error => console.error(error));
Async/Await — Đọc như code thông thường
async function fetchData() {
try {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
const detailResponse = await fetch(`https://api.example.com/detail/${data.id}`);
const detailData = await detailResponse.json();
console.log(detailData);
} catch (error) {
console.error("Lỗi:", error);
}
}
Code đọc từ trên xuống như code thông thường, nhưng thực ra hoạt động bất đồng bộ. Đây là lý do Async/Await được ưa chuộng nhất.
Cú pháp chi tiết
Từ khóa async
// async function luôn trả về Promise
async function myFunction() {
return "Xin chào";
}
const myFunc = async () => "Xin chào";
// Gọi hàm
myFunction().then(result => console.log(result)); // "Xin chào"
Từ khóa await
async function example() {
// Dừng lại và chờ Promise hoàn thành
const result = await somePromise();
// Code sau đây chỉ chạy sau khi Promise xong
console.log(result);
}
Quy tắc quan trọng: await chỉ dùng được BÊN TRONG async function.
Xử lý lỗi với try/catch
Đây là bước nhiều người bỏ qua nhưng cực kỳ quan trọng — network error, server error đều có thể xảy ra bất cứ lúc nào.
async function fetchUser(id) {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
// Fetch không tự báo lỗi HTTP — phải kiểm tra thủ công
if (!response.ok) {
throw new Error(`Lỗi HTTP: ${response.status}`);
}
const user = await response.json();
return user;
} catch (error) {
console.error("Không thể lấy dữ liệu:", error.message);
return null;
}
}
fetchUser(1).then(user => {
if (user) console.log(user.name);
});
Ví dụ thực tế: Gọi API và hiển thị dữ liệu
Chạy thử trong Console của trình duyệt (F12 → Console) — dùng JSONPlaceholder, API miễn phí cho học tập:
async function getBaiViet() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const posts = await response.json();
posts.slice(0, 5).forEach(post => {
console.log(`${post.id}. ${post.title}`);
});
} catch (error) {
console.error("Lỗi khi tải bài viết:", error);
}
}
getBaiViet();
Chạy song song với Promise.all
Khi có nhiều request độc lập với nhau, chạy song song nhanh hơn nhiều so với chạy tuần tự:
// Không hiệu quả: chờ từng cái một
async function fetchTuanTu() {
const user = await fetchUser(1); // Chờ ~1 giây
const posts = await fetchPosts(); // Chờ thêm ~1 giây
// Tổng: ~2 giây
}
// Hiệu quả hơn: chạy song song
async function fetchSongSong() {
const [user, posts] = await Promise.all([
fetchUser(1),
fetchPosts()
]);
// Tổng: ~1 giây (bằng request chậm nhất)
console.log(user, posts);
}
So sánh Callback, Promise và Async/Await
| Tiêu chí | Callback | Promise | Async/Await |
|---|---|---|---|
| Cú pháp | Lồng nhau nhiều | .then().catch() | try/catch |
| Dễ đọc | Kém | Trung bình | Tốt nhất |
| Debug | Khó | Trung bình | Dễ |
| ES version | ES5 | ES6 (2015) | ES2017 |
Câu hỏi thường gặp
await có làm đơ toàn bộ trang không?
Không. await chỉ tạm dừng bên trong async function đó. Main thread (UI) vẫn hoạt động bình thường — người dùng vẫn click và cuộn trang được.
Async/Await và Promise khác nhau thế nào?
Async/Await được xây dựng trên Promise — bản chất giống nhau, nhưng cú pháp đẹp hơn và dễ đọc hơn.
Có dùng await ngoài async function không?
Trong ES module (type="module") có thể dùng "top-level await". Nhưng thông thường, hãy bọc trong async function để an toàn.
Muốn áp dụng ngay? Xem thêm DOM trong JavaScript để kết hợp async/await với thao tác giao diện.