Promise là gì?
Promise là một object đạ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ộ trong tương lai. Promise giúp code JavaScript dễ đọc và dễ quản lý hơn so với callback truyền thống.
Vấn đề với Callback Hell
Trước khi có Promise, chúng ta thường gặp “Callback Hell”:
getUser(userId, function(user) {
getOrders(user.id, function(orders) {
getOrderDetails(orders[0].id, function(details) {
console.log(details);
// Càng lúc càng lồng sâu...
});
});
});
Cú pháp Promise
Tạo Promise
const myPromise = new Promise((resolve, reject) => {
// Thực hiện tác vụ bất đồng bộ
const success = true;
if (success) {
resolve('Thành công!');
} else {
reject('Có lỗi xảy ra!');
}
});
3 trạng thái của Promise
- Pending: Đang chờ xử lý
- Fulfilled: Hoàn thành thành công (resolve được gọi)
- Rejected: Thất bại (reject được gọi)
Sử dụng Promise
then() và catch()
myPromise
.then(result => {
console.log(result); // "Thành công!"
})
.catch(error => {
console.error(error);
});
Ví dụ thực tế: Fetch API
fetch('https://api.example.com/users')
.then(response => response.json())
.then(users => {
console.log('Danh sách users:', users);
})
.catch(error => {
console.error('Lỗi:', error);
});
Promise Chaining
Promise có thể chain (nối tiếp) để thực hiện nhiều tác vụ tuần tự:
getUser(userId)
.then(user => {
console.log('User:', user.name);
return getOrders(user.id);
})
.then(orders => {
console.log('Orders:', orders);
return getOrderDetails(orders[0].id);
})
.then(details => {
console.log('Order Details:', details);
})
.catch(error => {
console.error('Error:', error);
});
Lưu ý: Phải return Promise trong .then() để chain tiếp.
Các phương thức Promise tĩnh
Promise.all()
Chờ tất cả promises hoàn thành:
const promise1 = fetch('/api/users');
const promise2 = fetch('/api/products');
const promise3 = fetch('/api/orders');
Promise.all([promise1, promise2, promise3])
.then(([users, products, orders]) => {
console.log('Tất cả đã load xong!');
})
.catch(error => {
console.error('Có ít nhất 1 promise lỗi:', error);
});
Lưu ý: Nếu 1 promise reject, toàn bộ Promise.all() sẽ reject.
Promise.allSettled()
Chờ tất cả promises hoàn thành (dù thành công hay thất bại):
const promises = [
fetch('/api/users'),
fetch('/api/invalid-endpoint'),
fetch('/api/products')
];
Promise.allSettled(promises)
.then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${index} thành công:`, result.value);
} else {
console.log(`Promise ${index} thất bại:`, result.reason);
}
});
});
Promise.race()
Trả về kết quả của promise hoàn thành đầu tiên:
const promise1 = new Promise(resolve => setTimeout(() => resolve('Chậm'), 2000));
const promise2 = new Promise(resolve => setTimeout(() => resolve('Nhanh'), 500));
Promise.race([promise1, promise2])
.then(result => {
console.log(result); // "Nhanh"
});
Use case: Timeout cho requests:
const fetchWithTimeout = (url, timeout = 5000) => {
return Promise.race([
fetch(url),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout!')), timeout)
)
]);
};
Promise.any()
Trả về promise thành công đầu tiên:
const promise1 = Promise.reject('Lỗi 1');
const promise2 = new Promise(resolve => setTimeout(() => resolve('OK'), 1000));
const promise3 = new Promise(resolve => setTimeout(() => resolve('OK 2'), 2000));
Promise.any([promise1, promise2, promise3])
.then(result => {
console.log(result); // "OK"
});
Xử lý lỗi nâng cao
finally()
Luôn chạy dù Promise thành công hay thất bại:
fetch('/api/data')
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error(error);
})
.finally(() => {
console.log('Cleanup code - luôn chạy');
hideLoadingSpinner();
});
Xử lý lỗi trong chain
getUserData(userId)
.then(user => {
if (!user.isActive) {
throw new Error('User không active');
}
return user;
})
.then(user => getOrders(user.id))
.catch(error => {
console.error('Lỗi:', error.message);
// Có thể return giá trị mặc định
return [];
})
.then(orders => {
console.log('Orders:', orders);
});
Ví dụ thực tế
1. API Call với error handling
function fetchUserData(userId) {
return fetch(`/api/users/${userId}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('User data:', data);
return data;
})
.catch(error => {
console.error('Fetch error:', error);
throw error;
});
}
2. Load nhiều resources song song
async function loadDashboard() {
const [users, products, stats] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/products').then(r => r.json()),
fetch('/api/stats').then(r => r.json())
]);
return { users, products, stats };
}
3. Retry logic
function fetchWithRetry(url, retries = 3) {
return fetch(url)
.catch(error => {
if (retries > 0) {
console.log(`Retry... (${retries} lần còn lại)`);
return fetchWithRetry(url, retries - 1);
}
throw error;
});
}
Promise vs Async/Await
Promise và async/await thường được dùng cùng nhau:
// Với Promise
function getUser() {
return fetch('/api/user')
.then(r => r.json())
.then(user => {
return fetch(`/api/orders/${user.id}`);
})
.then(r => r.json());
}
// Với Async/Await (dễ đọc hơn)
async function getUser() {
const userResponse = await fetch('/api/user');
const user = await userResponse.json();
const ordersResponse = await fetch(`/api/orders/${user.id}`);
return ordersResponse.json();
}
Best Practices
- Luôn có .catch() để xử lý lỗi
- Return promises trong .then() khi chain
- Sử dụng finally() cho cleanup code
- Dùng Promise.all() để chạy song song thay vì tuần tự
- Cân nhắc async/await cho code dễ đọc hơn
- Tránh nested promises - dùng chaining thay thế
Kết luận
Promise là nền tảng của lập trình bất đồng bộ trong JavaScript hiện đại. Hiểu rõ Promise sẽ giúp bạn:
- Viết code bất đồng bộ rõ ràng hơn
- Xử lý lỗi tốt hơn
- Kết hợp tốt với async/await
- Tránh callback hell
Hãy thực hành và làm quen với Promise để trở thành một JavaScript developer giỏi hơn!