Callback Hell là gì?
Trong những ngày đầu của JavaScript bất đồng bộ, chúng ta xử lý mọi thứ bằng callback. Mỗi tác vụ bất đồng bộ nhận một hàm callback để gọi khi hoàn thành. Nghe có vẻ ổn — cho đến khi bạn cần nhiều tác vụ liên tiếp nhau.
// Callback hell — "Pyramid of Doom"
getUserById(userId, function(user) {
getPostsByUser(user.id, function(posts) {
getCommentsByPost(posts[0].id, function(comments) {
getAuthorOfComment(comments[0].id, function(author) {
// Bắt đầu mất kiểm soát...
console.log(author);
}, handleError);
}, handleError);
}, handleError);
}, handleError);Code này không chỉ khó đọc, mà còn khó maintain và debug. Đây là lý do cộng đồng JavaScript đau đầu và đẩy ra giải pháp: Promise.
Promise — Bước tiến đầu tiên
Promise đại diện cho một giá trị sẽ có trong tương lai. Thay vì truyền callback vào mỗi hàm, bạn nhận lại một đối tượng Promise và chain các tác vụ bằng .then().
// Promise chain — sạch hơn nhiều
getUserById(userId)
.then(user => getPostsByUser(user.id))
.then(posts => getCommentsByPost(posts[0].id))
.then(comments => getAuthorOfComment(comments[0].id))
.then(author => console.log(author))
.catch(err => console.error('Lỗi:', err));Async/Await — Cú pháp thay đổi cuộc chơi
ES2017 giới thiệu async/await, cho phép viết code bất đồng bộ trông giống code đồng bộ. Bên dưới vẫn là Promise, nhưng syntax đẹp hơn rất nhiều.
async function getAuthorInfo(userId) {
try {
const user = await getUserById(userId);
const posts = await getPostsByUser(user.id);
const comments = await getCommentsByPost(posts[0].id);
const author = await getAuthorOfComment(comments[0].id);
// Có thể dùng cả user lẫn author ở đây!
return { user, author };
} catch (err) {
console.error('Lỗi:', err);
throw err;
}
}Chạy song song với Promise.all
Một sai lầm phổ biến là dùng await tuần tự cho các tác vụ độc lập — làm chậm code không cần thiết.
// ❌ Tuần tự — tổng ~600ms
const user = await fetchUser(); // 200ms
const posts = await fetchPosts(); // 200ms
const weather = await fetchWeather(); // 200ms
// ✅ Song song — tổng ~200ms
const [user, posts, weather] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchWeather(),
]);Tip: DùngPromise.allSettled()thay vìPromise.all()khi muốn tất cả tác vụ hoàn thành dù có lỗi hay không.
Xử lý lỗi đúng cách
Một pattern hay là tạo wrapper function trả về tuple [error, data]:
const to = (promise) =>
promise
.then(data => [null, data])
.catch(err => [err, null]);
async function loadProfile(id) {
const [err, user] = await to(fetchUser(id));
if (err) { console.error('Không tìm thấy user'); return null; }
return user;
}Kết luận
Async/await không phải là “magic” — nó là syntactic sugar trên Promise. Một vài điểm cần nhớ:
- Dùng
Promise.all()cho các tác vụ độc lập chạy song song - Luôn handle lỗi — dù bằng
try/catchhay.catch() - Hàm
asyncluôn trả về Promise, dù bạn khôngreturngì awaitchỉ dùng được trongasyncfunction