Callback hell, Promise, Async await
date
‣
thumbnail
slug
callback-hell-promise-async-await
status
Published
tags
Javascript
summary
type
Post
Callback hell là gì?
Giải thích ngắn gọn là khi các hàm callback của bạn được chèn vào nhau nhiều lần, gây khó khăn trong việc quản lý. Điều này có thể xảy ra trong bất kỳ ngôn ngữ lập trình nào và thường xuất hiện khi xử lý các tác vụ không đồng bộ. Một hàm callback promise trong JavaScript là một ví dụ phức tạp cụ thể được miêu tả trong đó.
// A typical callback hell which involves multiple JavaScript promises
firstPromise
.then(secondPromise
.then(thirdPromise
.then(...).catch(...)
).catch(...)
).catch(...)Đọc tuy dễ hiểu, nhưng callback hell có thể gây ra các vấn đề khác về scope. Một vấn đề thông dụng là hide error (swallowing) khi một error được gửi bởi một hàm callback bên trong không được catch.
// Asynchronous API call operation
getWeatherTodayPromise
.then((weatherForecast) => {
// Asynchronous IO operation
writeWeatherForecastToLogFilePromise(weatherForecast) // FAILED
// Unlike try-catch, there is no outer "catch-all" solution
// You must have a "catch" at every nested promise
// Or else, the promise is not terminated, and the error information is lost
.catch((error) => {
// IO error is caught here
console.error("Inner promise", error)
})
})
.catch((error) = > {
// IO error is NOT caught here
console.error("Outer promise", error)
})Promises là gì?
Promise là cách thay thế cho hàm callback để trả kết quả từ các tác vụ bất đồng bộ. Sử dụng promise có thể yêu cầu nhiều công sức hơn so với sử dụng hàm callback, nhưng cũng cung cấp nhiều lợi ích cho người dùng. Promise dễ đọc hơn và có nhiều ứng dụng như lấy dữ liệu từ một nguồn, thao tác trong mongoose và nhiều hơn nữa. Ví dụ, xem cách sử dụng promise trong ví dụ trên. Thực ra, promise có bốn trạng thái khác nhau:
- fulfilled: Hành động liên quan đến promise đã thành công
- rejected: Hành động liên quan đến promise đã thất bại
- pending: Chưa thực hiện hoặc từ chối
- settled: Đã thực hiện hoặc từ chối

Trước tiên, chúng ta phải tạo promise, để hiểu điều này:
function getUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Reading from a database....");
resolve({ id: id, githubUsername: "jerrycode06" });
}, 2000);
});
}
function getRepositories(username) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`Extracting Repositories for ${username}....`);
resolve(["repo1", "repo2", "repo3"]);
// reject(new Error("Error occured in repositories"));
}, 2000);
});
}
function getCommits(repo) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Extracting Commits for " + repo + "....");
resolve(["commits"]);
}, 2000);
});
}Chúng ta đã tạo 3 function, thay vì gọi hàm callback, chúng ta bây giờ trả về một Promise có hai tham số resolve và reject. Nếu mọi thứ hoạt động được, gọi resolve, ngược lại gọi reject. Hãy xem cách sử dụng promise -
// Replace Callback with Promises to avoid callback hell
getUser(1)
.then((user) => getRepositories(user.githubUsername))
.then((repos) => getCommits(repos[0]))
.then((commits) => console.log("Commits", commits))
.catch((err) => console.log("Error: ", err.message));Dễ đọc hơn chứ, Đúng không? Sử dụng arrow function đã làm cho điều này ít phức tạp hơn so với việc sử dụng các function truyền thống. Chúng ta đã tránh việc lồng giữa các function và giảm sự phức tạp về viết code (phương pháp hàm callback) và đó là cách hoạt động của promise. Bạn có thể tìm hiểu thêm về promise tại đây.
Async/await là gì?
Cách viết promise bằng async/await được coi là tốt hơn và giúp cho việc viết code đơn giản và sạch sẽ hơn. Để sử dụng async/await, bạn chỉ cần viết từ async trước tên hàm bình thường và nó sẽ trở thành một promise. Nói cách khác, async/await là một cách sử dụng promise, nghĩa là nếu bạn muốn tránh việc viết nhiều dòng .then(), bạn có thể sử dụng async/await để viết code gọn hơn. Xem ví dụ dưới đây để thấy cách sử dụng nó.
// Async- await approach
async function displayCommits() {
try {
const user = await getUser(1);
const repos = await getRepositories(user.githubUsername);
const commits = await getCommits(repos[0]);
console.log(commits);
} catch (err) {
console.log("Error: ", err.message);
}
}
displayCommit();Bây giờ, nó dễ đọc hơn so với việc sử dụng class Promise. Mỗi khi chúng ta sử dụng await, chúng ta cần assign cho nó với một hàm async. Giống như promise, chúng ta không có phương thức catch() ở đây nên đó là lý do tại sao chúng ta sử dụng khối try-catch để xử lý lỗi.
Các câu hỏi phỏng vấn về callback hell, async/await, promise
- Có thể giải thích gì về "callback hell"?
- Làm thế nào để tránh "callback hell" trong JavaScript?
- Cách sử dụng async/await để xử lý các tác vụ bất đồng bộ?
- Làm sao để giải quyết vấn đề "unhandled promise rejection" trong JavaScript? Trả lời: "Unhandled promise rejection" là một lỗi xảy ra khi một promise được tạo ra nhưng không được xử lý bằng catch hoặc bằng một hàm then. Để giải quyết vấn đề này, bạn có thể sử dụng hàm catch hoặc then để xử lý promise. Bạn có thể thêm catch hoặc then vào cuối chuỗi promise hoặc sử dụng global unhandledrejection event để xử lý các promise không được xử lý.
promiseFunction()
.then(response => { /* handle response / })
.catch(error => { / handle error */ });
window.addEventListener('unhandledrejection', event => {
// handle event.promise and event.reason
});Chú ý: nếu bạn sử dụng async/await thì try-catch sẽ xử lý các lỗi từ promise.
try {
const response = await promiseFunction();
// handle response
} catch (error) {
// handle error
}- Cách sử dụng promise để xử lý các tác vụ bất đồng bộ?
- Tại sao chúng ta nên sử dụng async/await hoặc promise thay vì callback?
Tóm cái quần lại:
Trong bài viết này, chúng ta đã tìm hiểu về:
- Synchronous vs Asynchronous
- Hàm callback và "Callback hell"
- Tránh "Callback hell" bằng cách sử dụng promise và async/await
Tôi cá nhân thích phương pháp async/await nhất nhưng thỉnh thoảng chúng ta cũng cần sử dụng phương pháp promise để xử lý các tác vụ bất đồng bộ.
Cảm ơn bạn đã đọc bài viết dài này! Tôi hy vọng nó giúp bạn hiểu rõ hơn về những chủ đề này. Nếu bạn thích bài viết này, hãy cho tôi một vài ❤️ và chia sẻ nó nếu có thể. Bạn có thể để lại bình luận với những gợi ý hoặc hỏi bất cứ điều gì.