Promise trong JavaScript là gì? Cách dùng cơ bản
Khi đọc code JavaScript, bạn có thể thấy các đoạn code với .then() và .catch() mà không hiểu chúng làm gì.
Đây là cú pháp của Promise — một trong những khái niệm quan trọng nhất trong JavaScript hiện đại.
Nếu bạn chưa biết JavaScript là gì, hãy đọc bài JavaScript là gì? Giới thiệu cho người mới bắt đầu trước nhé.
Sau bài này, bạn sẽ hiểu được:
- Promise là gì và tại sao cần dùng
- 3 trạng thái của Promise
- Cú pháp
new Promise(),.then(),.catch() - Promise chaining và
Promise.all() - Tại sao Promise tốt hơn Callback
Promise trong JavaScript là gì?
Promise là một đối tượng (object) đại diện cho kết quả của một thao tác bất đồng bộ. Tên "Promise" nghĩa là "lời hứa" — hứa rằng sẽ trả về kết quả trong tương lai, dù thành công hay thất bại.
Hãy tưởng tượng bạn đặt pizza online. Sau khi đặt, bạn nhận được một "lời hứa" giao hàng trong 30 phút. Trong lúc chờ, bạn vẫn có thể làm việc khác. Sau 30 phút, nếu pizza đến thì "thành công" (fulfilled), nếu hết hàng thì "thất bại" (rejected). Promise trong JavaScript hoạt động y hệt như vậy.
JavaScript là ngôn ngữ đơn luồng (single-threaded). Nếu chờ đồng bộ khi gọi API hay đọc file, trình duyệt sẽ bị "đóng băng". Promise được giới thiệu trong ES6 (2015) để xử lý bất đồng bộ một cách rõ ràng và dễ quản lý hơn.
3 Trạng thái của Promise
Mỗi Promise luôn ở một trong ba trạng thái:
| Trạng thái | Ý nghĩa |
|---|---|
| Pending | Đang chờ xử lý — chưa có kết quả |
| Fulfilled | Thao tác thành công — resolve() được gọi |
| Rejected | Thao tác thất bại — reject() được gọi |
Điểm quan trọng: một khi Promise đã chuyển sang Fulfilled hoặc Rejected, trạng thái đó không thể thay đổi nữa. Điều này giúp bạn xử lý kết quả một cách đáng tin cậy.
Cú pháp cơ bản của Promise
Để tạo một Promise, bạn dùng new Promise() với một hàm executor bên trong:
const myPromise = new Promise((resolve, reject) => {
// Thao tác bất đồng bộ của bạn ở đây
const success = true;
if (success) {
resolve("Thành công!"); // Chuyển sang Fulfilled
} else {
reject("Thất bại..."); // Chuyển sang Rejected
}
});
myPromise
.then(result => console.log(result)) // In: "Thành công!"
.catch(error => console.log(error)); // Không chạy vì success = true
Giải thích từng phần:
new Promise((resolve, reject) => {...})— Tạo Promise mới với hàm executorresolve(value)— Gọi khi thành công;valuesẽ được truyền vào.then()reject(reason)— Gọi khi thất bại;reasonsẽ được truyền vào.catch().then(callback)— Chạy khi Promise fulfilled.catch(callback)— Chạy khi Promise rejected
Ví dụ thực tế — Giả lập gọi API với setTimeout
function layDuLieuNguoiDung(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId > 0) {
resolve({ id: userId, ten: "Nguyen Van A", tuoi: 22 });
} else {
reject(new Error("userId không hợp lệ"));
}
}, 1000); // Giả lập delay 1 giây
});
}
layDuLieuNguoiDung(1)
.then(nguoiDung => {
console.log("Tìm thấy:", nguoiDung.ten); // "Tìm thấy: Nguyen Van A"
})
.catch(loi => {
console.error("Lỗi:", loi.message);
});
Trong ví dụ này, setTimeout mô phỏng thao tác tốn thời gian (như gọi API thực tế).
Sau 1 giây, nếu userId hợp lệ thì Promise fulfilled với dữ liệu người dùng, ngược lại bị rejected.
Promise Chaining — Nối tiếp nhiều Promise
Một trong những tính năng mạnh nhất của Promise là khả năng nối tiếp (chain) nhiều thao tác bất đồng bộ. Thay vì lồng nhau, bạn có thể viết theo dạng thẳng hàng, dễ đọc hơn nhiều:
fetch('https://api.example.com/data')
.then(response => response.json()) // Bước 1: Parse JSON từ response
.then(data => {
console.log(data); // Bước 2: Dùng dữ liệu
return data.userId; // Trả về userId cho bước tiếp theo
})
.then(userId => {
return fetch(`https://api.example.com/users/${userId}`);
})
.then(response => response.json())
.then(user => console.log(user.name)) // Bước cuối: In tên người dùng
.catch(error => console.error(error)); // Bắt lỗi từ bất kỳ bước nào
Những điểm cần nhớ về Promise chaining:
- Mỗi
.then()nhận giá trị return của.then()trước đó - Nếu
.then()trả về một Promise mới, chuỗi sẽ chờ Promise đó hoàn thành - Một
.catch()ở cuối bắt được lỗi từ tất cả các bước trong chuỗi
Promise.all() — Chạy nhiều Promise song song
Khi cần nhiều tác vụ chạy đồng thời (ví dụ: load nhiều API một lúc để tiết kiệm thời gian),
hãy dùng Promise.all():
const layUser1 = fetch('https://api.example.com/users/1').then(r => r.json());
const layUser2 = fetch('https://api.example.com/users/2').then(r => r.json());
const layUser3 = fetch('https://api.example.com/users/3').then(r => r.json());
Promise.all([layUser1, layUser2, layUser3])
.then(([user1, user2, user3]) => {
console.log(user1.name, user2.name, user3.name);
})
.catch(error => {
console.error("Một trong các request thất bại:", error);
});
Đặc điểm của Promise.all():
- Fulfilled khi tất cả Promise trong mảng đều fulfilled
- Rejected ngay lập tức nếu bất kỳ một Promise nào bị rejected
- Kết quả trả về là mảng, theo đúng thứ tự input
- Các Promise chạy song song → nhanh hơn chạy tuần tự
Mẹo: Nếu bạn muốn chờ tất cả kể cả khi có lỗi, hãy dùng
Promise.allSettled() thay vì Promise.all().
Promise vs Callback Hell — Tại sao Promise tốt hơn?
Trước khi có Promise, lập trình viên xử lý bất đồng bộ bằng Callback trong JavaScript. Callback đơn giản thì ổn, nhưng khi lồng nhiều cấp sẽ tạo ra "Callback Hell" (hay còn gọi là "Pyramid of Doom"):
// Callback Hell — khó đọc, khó debug, khó bảo trì
getData(function(a) {
getMoreData(a, function(b) {
getEvenMoreData(b, function(c) {
getYetMoreData(c, function(d) {
console.log(d); // Thụt lề sâu tới mức khó theo dõi
}, errorHandler);
}, errorHandler);
}, errorHandler);
}, errorHandler);
Cùng đoạn logic đó, viết bằng Promise:
// Promise Chain — dễ đọc, dễ bảo trì
getData()
.then(a => getMoreData(a))
.then(b => getEvenMoreData(b))
.then(c => getYetMoreData(c))
.then(d => console.log(d))
.catch(errorHandler); // Một chỗ xử lý lỗi cho toàn bộ chuỗi
Lý do Promise tốt hơn Callback:
- Dễ đọc hơn — Cấu trúc tuyến tính, không bị "thụt lề vô tận"
- Xử lý lỗi tập trung — Một
.catch()cho toàn bộ chuỗi - Tránh Callback Hell — Không còn "Pyramid of Doom"
- Dễ thêm/bỏ bước xử lý — Chỉ cần thêm/xóa
.then() - Nền tảng cho async/await — Cú pháp hiện đại nhất trong JavaScript
Bước tiếp theo: async/await
Bạn đã nắm vững Promise — đây là nền tảng quan trọng nhất! Bước tiếp theo là học async/await, cú pháp được giới thiệu trong ES2017 giúp viết code bất đồng bộ trông giống code đồng bộ, dễ đọc hơn nhiều.
// Cùng chức năng, nhưng async/await dễ đọc hơn Promise chain
async function layDuLieu() {
const response = await fetch('/api/data'); // Chờ fetch xong
const data = await response.json(); // Chờ parse JSON xong
return data.name;
}
Điều quan trọng cần nhớ: async/await được xây dựng trên nền Promise.
Hiểu Promise rõ ràng là tiền đề để học async/await hiệu quả.
Hãy đọc bài tiếp theo: async/await trong JavaScript — Cách viết code bất đồng bộ hiện đại
Tóm tắt
- Promise là đối tượng đại diện cho kết quả của thao tác bất đồng bộ
- Ba trạng thái:
pending→fulfilled(thành công) hoặcrejected(thất bại) new Promise((resolve, reject) => {...})để tạo Promise.then()xử lý kết quả thành công,.catch()xử lý lỗi- Promise chaining cho phép nối nhiều bước xử lý bất đồng bộ
Promise.all()chạy nhiều Promise song song, nhanh hơn tuần tự- Promise giải quyết vấn đề Callback Hell, code dễ đọc và bảo trì hơn
async/awaitlà bước tiếp theo, xây dựng trực tiếp trên Promise