Promise, async/await và cách tránh callback hell
4 phút
Xử lý code bất đồng bộ là một trong những thách thức lớn nhất khi học JavaScript. Hãy cùng tìm hiểu cách giải quyết vấn đề “callback hell” và viết code async dễ đọc hơn.
Vấn đề: Callback Hell 🔥
Khi làm việc với nhiều tác vụ bất đồng bộ (gọi API, đọc file…), code callback lồng nhau trở nên khó đọc:
// ❌ Callback Hell - Khó đọc, khó maintain
getUserData(userId, (user) => {
getPostsByUser(user.id, (posts) => {
getComments(posts[0].id, (comments) => {
getLikes(comments[0].id, (likes) => {
console.log('Số lượt like:', likes.length);
// Còn nhiều cấp nữa...
});
});
});
});Giải pháp 1: Promise Chain ⛓️
Promise giúp code “phẳng” hơn với chuỗi .then():
// ✅ Promise Chain - Dễ đọc hơn
getUserData(userId)
.then(user => getPostsByUser(user.id))
.then(posts => getComments(posts[0].id))
.then(comments => getLikes(comments[0].id))
.then(likes => {
console.log('Số lượt like:', likes.length);
})
.catch(error => {
console.error('Có lỗi xảy ra:', error);
});Promise cơ bản
// Tạo Promise
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve('Thành công!');
} else {
reject('Lỗi rồi!');
}
}, 1000);
});
// Sử dụng Promise
myPromise
.then(result => console.log(result))
.catch(error => console.error(error));Giải pháp 2: Async/Await ⚡
Async/await làm code bất đồng bộ trông giống code đồng bộ:
// ✅ Async/Await - Dễ đọc nhất!
async function fetchAllData(userId) {
try {
const user = await getUserData(userId);
const posts = await getPostsByUser(user.id);
const comments = await getComments(posts[0].id);
const likes = await getLikes(comments[0].id);
console.log('Số lượt like:', likes.length);
} catch (error) {
console.error('Có lỗi xảy ra:', error);
}
}
fetchAllData(123);Async/Await cơ bản
// Hàm async luôn trả về Promise
async function greet() {
return 'Xin chào!';
}
// Tương đương với:
function greet() {
return Promise.resolve('Xin chào!');
}
// Await chỉ dùng được trong hàm async
async function getData() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
}So sánh 3 phiên bản cùng 1 logic 🔄
Ví dụ: Lấy thông tin user, rồi lấy bài post đầu tiên của user đó.
Version 1: Callback (😰 Khó đọc)
function getFirstPost(userId, callback) {
getUser(userId, (error, user) => {
if (error) {
callback(error, null);
return;
}
getPosts(user.id, (error, posts) => {
if (error) {
callback(error, null);
return;
}
callback(null, posts[0]);
});
});
}
// Sử dụng
getFirstPost(1, (error, post) => {
if (error) {
console.error('Lỗi:', error);
} else {
console.log('Bài post:', post);
}
});Version 2: Promise (😊 Tốt hơn)
function getFirstPost(userId) {
return getUser(userId)
.then(user => getPosts(user.id))
.then(posts => posts[0]);
}
// Sử dụng
getFirstPost(1)
.then(post => console.log('Bài post:', post))
.catch(error => console.error('Lỗi:', error));Version 3: Async/Await (🎉 Tuyệt vời nhất!)
async function getFirstPost(userId) {
const user = await getUser(userId);
const posts = await getPosts(user.id);
return posts[0];
}
// Sử dụng
async function main() {
try {
const post = await getFirstPost(1);
console.log('Bài post:', post);
} catch (error) {
console.error('Lỗi:', error);
}
}
main();Error Handling: Xử lý lỗi đúng cách 🛡️
Promise - Dùng .catch()
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Lỗi:', error))
.finally(() => console.log('Hoàn thành!'));Async/Await - Dùng try/catch
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Lỗi:', error);
} finally {
console.log('Hoàn thành!');
}
}Xử lý nhiều Promise cùng lúc
// Chạy song song - Nhanh hơn
async function fetchMultiple() {
try {
const [users, posts, comments] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
console.log('Users:', users);
console.log('Posts:', posts);
console.log('Comments:', comments);
} catch (error) {
console.error('Có API nào đó bị lỗi:', error);
}
}
// Promise.allSettled - Không dừng khi có lỗi
async function fetchMultipleSafe() {
const results = await Promise.allSettled([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`API ${index} thành công:`, result.value);
} else {
console.log(`API ${index} lỗi:`, result.reason);
}
});
}Tips quan trọng 💡
- Luôn xử lý lỗi: Đừng quên
.catch()hoặctry/catch - Async/await dễ đọc hơn Promise chain cho code phức tạp
- Dùng Promise.all() khi các tác vụ độc lập (chạy song song)
- Await tuần tự khi tác vụ sau phụ thuộc vào tác vụ trước
- Top-level await chỉ dùng được trong ES modules
// ❌ Chậm - Chạy tuần tự không cần thiết
const user = await getUser(1);
const settings = await getSettings(); // Không phụ thuộc user
// ✅ Nhanh - Chạy song song
const [user, settings] = await Promise.all([
getUser(1),
getSettings()
]);Kết luận
- Callback: Cũ, dễ bị callback hell
- Promise: Cải thiện, nhưng vẫn hơi phức tạp với
.then()chain - Async/Await: Modern, dễ đọc nhất, nên dùng cho mọi code mới
Hãy luôn ưu tiên async/await trong code mới và nhớ xử lý lỗi đúng cách! 🚀
