Callback hell là khi các callback bất đồng bộ lồng vào nhau nhiều tầng, code thụt lề thành hình kim tự tháp và code xử lý lỗi bị lặp lại ở mọi tầng.
javascript
getUser(id, (err, user) => {
if (err) return handle(err)
getPosts(user.id, (err, posts) => {
if (err) return handle(err)
getComments(posts[0].id, (err, comments) => {
if (err) return handle(err)
render(user, posts, comments)
})
})
})Hai vấn đề thật, không chỉ là xấu: lỗi phải xử lý lặp ở từng tầng, và không dùng được try/catch.
Viết lại bằng async/await:
javascript
try {
const user = await getUser(id)
const posts = await getPosts(user.id)
const comments = await getComments(posts[0].id)
render(user, posts, comments)
} catch (err) {
handle(err) // MỘT chỗ bắt lỗi cho cả chuỗi
}Và chạy song song khi các bước độc lập — điểm mà bản callback rất khó viết đúng:
javascript
const [profile, settings] = await Promise.all([getProfile(id), getSettings(id)])Ba cách xử lý, xếp theo thứ tự nên dùng:
1. async/await — mặc định.
2. Promise chaining — khi cần truyền tiếp theo kiểu pipeline.
3. Tách hàm đặt tên — cứu chữa nhanh cho code cũ chưa chuyển được sang Promise.
Ghi chú: API cũ chỉ có callback thì bọc lại bằng util.promisify (Node) hoặc new Promise một lần, rồi dùng await như bình thường.