Callback vs Promises vs Async/Await: Hướng dẫn toàn tập
Học lập trình bất đồng bộ trong JavaScript với callbacks, promises và async/await. Tránh "callback hell" và viết mã sạch hơn, dễ bảo trì hơn với các ví dụ thực tế. Hãy cùng tìm hiểu kỹ hơn trong bài viết ngay sau đây!
Callbacks là gì?
- Callback là một hàm được truyền làm đối số cho một hàm khác và sẽ được thực thi sau đó (đồng bộ hoặc bất đồng bộ).
- Callbacks đóng vai trò quan trọng trong JavaScript để xử lý các thao tác bất đồng bộ, lắng nghe sự kiện và lập trình hàm.
Ví dụ minh họa cơ bản:
function saveOrder(orderId, userMail, callback) {
const order = {
orderId,
userMail
}
scrib.show(order)
callback(userMail); // Execute the callback function
}
function sendOrderEmail(userMail) {
scrib.show(userMail, "Order confirmed")
}
saveOrder("145908275","user@gmail.com", sendOrderEmail);
Trường hợp Callback không đồng bộ:
- Chúng ta có thể lấy ví dụ về phương thức setTimeout vì nó thực thi không đồng bộ
console.log("Javascript is awesome");
setTimeout(() => {
console.log("This codeblock runs after 2 seconds");
}, 2000);
console.log("Scribbler is awesome");
// Output
Javascript is awesome
Scribbler is awesome
This codeblock runs after 2 seconds
Trường hợp Callback Hell:
- Khi các lệnh Callback được lồng vào nhau quá sâu, chúng sẽ trở nên khó đọc và khó bảo trì.
function saveOrder(orderId, userMail, callback) {
const order = {
orderId,
userMail
}
scrib.show(order)
callback(userMail); // Execute the callback function
}
function sendDetailsToSeller(orderId, userMail, callback) {
const details = {
orderId,
userMail
}
scrib.show(details)
callback(userMail);
}
function sendOrderEmail(userMail) {
scrib.show(userMail, "Order confirmed")
}
saveOrder("145908275","user@gmail.com", () => sendDetailsToSeller("145908275","user@gmail.com",sendOrderEmail));
Như vậy ta có thể kết luận rằng:
- Vấn đề khi sử dụng Callback: Khó đọc, bảo trì và gỡ lỗi.
- Giải pháp: Sử dụng Promises hoặc Async/Await để thay thế.
Promises – vị cứu tinh của Callback Hell
Promise trong JavaScript đại diện cho kết quả thành công hoặc thất bại của một thao tác bất đồng bộ. Nó cung cấp một cách tiếp cận sạch sẽ hơn so với callbacks truyền thống.
Một Promise nhận một hàm với hai tham số:
- resolve → Gọi khi tác vụ bất đồng bộ thành công.
- reject → Gọi khi tác vụ bất đồng bộ thất bại.
Trạng thái của Promise:
- Pending → Trạng thái ban đầu, chưa được hoàn thành hoặc từ chối.
- Fulfilled → Tác vụ hoàn thành thành công (resolve() được gọi).
- Rejected → Tác vụ thất bại (reject() được gọi).
Ví dụ cơ bản:
const basicPromise = new Promise((resolve, reject) => {
setTimeout(() => {
let success = true; // Simulating success or failure
if (success) {
resolve("Task completed successfully! ✅");
} else {
reject("Task failed ❌");
}
}, 2000);
});
basicPromise
.then((result) => {
scrib.show(result); // Output: Task completed successfully! ✅
})
.catch((error) => {
scrib.show(error); // Output: Task failed ❌ (if failed)
})
.finally(() => {
scrib.show("Promise execution completed.");
});
Lấy dữ liệu bằng phương pháp fetch:
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => console.log(data))
.catch(error => console.log("Fetch Error:", error));
Phương pháp nối các promise với nhau:
// Chaining promises method
new Promise((resolve) => {
setTimeout(() => resolve(10), 1000);
})
.then((num) => {
scrib.show(num); // 10
return num * 2;
})
.then((num) => {
scrib.show(num); // 20
return num * 3;
})
.then((num) => {
scrib.show(num); // 60
});
Các phương thức của Promise:
- Promise.all → Chờ tất cả Promise hoàn thành. Nếu một Promise bị reject, toàn bộ kết quả sẽ bị reject.
- Promise.race → Trả về Promise đầu tiên hoàn thành (fulfilled hoặc rejected).
- Promise.allSettled → Chờ tất cả Promise hoàn thành (dù thành công hay thất bại) và trả về trạng thái của từng Promise.
- Promise.any → Trả về Promise đầu tiên được fulfilled, bỏ qua các Promise bị rejected.
const promise1 = new Promise((resolve) => {
setTimeout(() => resolve(Math.floor(3.14), 1000));
})
const promise2 = new Promise((resolve) => {
setTimeout(() => resolve(Math.ceil(3.14), 1000));
})
const promise3 = new Promise((resolve) => {
setTimeout(() => resolve(Math.pow(3.14, 10), 1000));
})
// Promise.all
const promiseAll = Promise.all([promise1,promise2,promise3])
promiseAll.then(scrib.show())
// Promise.race
const promiseRace = Promise.race([promise1,promise2,promise3])
promiseRace.then(scrib.show())
// Promise.allSettled
const promiseAllSettled = Promise.allSettled([promise1,promise2,promise3])
promiseAllSettled.then(scrib.show())
// Promise.any
const promiseAny = Promise.any([promise1,promise2,promise3])
promiseAny.then(scrib.show())
Như vậy:
- Để xử lý async tốt hơn, có thể sử dụng async/await, giúp đơn giản hóa việc làm việc với promise.
Async/Await
async/await là một tính năng hiện đại của JavaScript giúp đơn giản hóa việc xử lý mã bất đồng bộ. Nó giúp bạn viết mã theo phong cách đồng bộ, dễ đọc hơn, dễ hiểu hơn và dễ debug hơn.
Tại sao nên dùng async/await?
Trước khi có async/await, các lập trình viên JavaScript phải xử lý bất đồng bộ bằng:
- Callbacks → Gây ra tình trạng "callback hell".
- Promises → Tốt hơn, nhưng vẫn cần .then() chaining.
Cách hoạt động của async/await
- Khai báo một hàm với async khiến hàm đó tự động trả về một Promise, ngay cả khi nó trả về một giá trị đơn giản.
Ví dụ cơ bản: Giải quyết vấn đề Callback hell
// Async await example for callback hell issue
async function saveOrder(orderId, userMail, callback) {
const order = {
orderId,
userMail
}
scrib.show(order)
return order
}
async function sendDetailsToSeller(orderId, userMail, callback) {
const details = {
orderId,
userMail
}
scrib.show(details)
return details
}
async function sendOrderEmail(userMail) {
scrib.show(userMail, "Order confirmed")
return "Email sent"
}
async function processOrder(orderId, userMail) {
await saveOrder(orderId, userMail);
await sendDetailsToSeller(orderId, userMail);
await sendOrderEmail(userMail);
}
processOrder("145908275","user@gmail.com");
Fetching data:
// Fetching data using async/await
async function fetchData() {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1'); // ✅ Await fetch
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json(); // ✅ Await response.json()
return data;
}
fetchData()
.then(data => scrib.show("Fetched Data:", data))
.catch(error => scrib.show("Error:", error));
Kết hợp vòng lặp với async/await cho các luồng có thể đọc được:
// Combining async/await with loops
async function fetchTodos() {
let urls = [
"https://jsonplaceholder.typicode.com/todos/1",
"https://jsonplaceholder.typicode.com/todos/2",
"https://jsonplaceholder.typicode.com/todos/3"
];
for (let url of urls) {
let response = await fetch(url);
let data = await response.json();
scrib.show(data);
}
}
fetchTodos();
Hy vọng qua bài viết này, các bạn đã có thể biết được nên sử dụng hàm nào để tiện lợi trong công việc lập trình hơn.
All rights reserved