Lập trình bất đồng bộ (asynchronous) là trái tim của JavaScript, giúp các ứng dụng web gọi API, đọc tệp tin hay truy vấn cơ sở dữ liệu mà không làm "treo" trình duyệt của người dùng. Trước đây, lập trình viên phải vật lộn với các hàm callback lồng nhau chằng chịt (Callback Hell). Sự ra đời của Promise trong JavaScript đã thay đổi hoàn toàn cục diện, mang lại cách viết code gọn gàng, dễ quản lý và chuyên nghiệp hơn.
Promise Trong JavaScript Là Gì?
Promise là một đối tượng đại diện cho sự hoàn thành (hoặc thất bại) của một thao tác bất đồng bộ và kết quả của nó trong tương lai.
Hãy tưởng tượng Promise giống như việc bạn đi mua trà sữa. Bạn thanh toán tiền và nhận lại một hóa đơn có số thứ tự. Tờ hóa đơn đó chính là một "Promise" (lời hứa) từ quán rằng họ sẽ giao trà sữa cho bạn. Trong lúc chờ đợi, bạn có thể lướt điện thoại làm việc khác. Cuối cùng, quán sẽ gọi số của bạn (thành công) hoặc thông báo hết trân châu (thất bại).
3 Trạng Thái Cốt Lõi Của Một Promise
Một Promise trong JavaScript luôn tồn tại ở một trong ba trạng thái (states) sau:
Pending (Đang chờ): Trạng thái ban đầu, thao tác chưa hoàn thành cũng chưa thất bại (đang pha trà sữa).
Fulfilled (Thành công): Thao tác đã hoàn thành thành công và trả về một kết quả (nhận được ly trà sữa).
Rejected (Thất bại): Thao tác thất bại và trả về một lý do hoặc lỗi (quán thông báo hết nguyên liệu).
Khi một Promise chuyển từ trạng thái Pending sang Fulfilled hoặc Rejected, nó được coi là đã Settled (đã giải quyết xong) và không thể thay đổi trạng thái được nữa.
Cách Khởi Tạo Và Sử Dụng Promise Cơ Bản
Để tạo ra một Promise mới, chúng ta sử dụng từ khóa new Promise truyền vào một hàm thực thi (executor function) với hai tham số resolve và reject.
// Khởi tạo Promise
const myPromise = new Promise((resolve, reject) => {
let isSuccess = true; // Giả lập trạng thái thao tác
setTimeout(() => {
if (isSuccess) {
resolve("Lấy dữ liệu thành công!"); // Chuyển sang Fulfilled
} else {
reject("Lỗi: Không thể kết nối đến máy chủ."); // Chuyển sang Rejected
}
}, 2000); // Giả lập thời gian chờ 2 giây
});
Sau khi có Promise, chúng ta xử lý kết quả trả về bằng các phương thức .then() và .catch():
// Sử dụng Promise
myPromise
.then((result) => {
// Chạy khi Promise được resolve (Thành công)
console.log(result);
})
.catch((error) => {
// Chạy khi Promise bị reject (Thất bại)
console.error(error);
})
.finally(() => {
// Luôn chạy ở cuối, bất kể thành công hay thất bại
console.log("Hoàn tất quá trình xử lý.");
});
Giải Quyết Callback Hell Bằng Promise Chaining
Điểm mạnh lớn nhất của cách sử dụng Promise là khả năng nối chuỗi (Chaining). Mỗi phương thức .then() lại trả về một Promise mới, cho phép bạn thực hiện các thao tác tuần tự một cách vô cùng sạch sẽ.
fetch('https://jsonplaceholder.typicode.com/users/1')
.then(response => {
if (!response.ok) throw new Error("Lỗi HTTP");
return response.json(); // Trả về Promise chứa dữ liệu JSON
})
.then(user => {
console.log("Tên User:", user.name);
// Tiếp tục lấy bài viết của user này
return fetch(`https://jsonplaceholder.typicode.com/posts?userId=${user.id}`);
})
.then(response => response.json())
.then(posts => {
console.log("Bài viết đầu tiên:", posts[0].title);
})
.catch(error => {
console.error("Lỗi trong toàn bộ chuỗi:", error);
});
Nếu có bất kỳ lỗi nào xảy ra ở bất kỳ mắt xích .then() nào, luồng thực thi sẽ nhảy thẳng xuống block .catch() duy nhất ở cuối cùng để xử lý.
Tăng Tốc Độ Trải Nghiệm Với Promise.all
Trong thực tế ứng dụng, bạn thường cần gọi nhiều API không liên quan đến nhau. Thay vì chờ cái này xong mới gọi cái kia (tuần tự), bạn có thể dùng Promise.all() để gọi tất cả cùng lúc (song song), giúp giảm đáng kể thời gian chờ.
const getPosts = fetch('https://jsonplaceholder.typicode.com/posts').then(res => res.json());
const getComments = fetch('https://jsonplaceholder.typicode.com/comments').then(res => res.json());
const getTags = fetch('https://jsonplaceholder.typicode.com/tags').then(res => res.json());
// Bắt đầu tính thời gian
console.time("FetchData");
Promise.all([getPosts, getComments, getTags])
.then(([posts, comments, tags]) => {
// Luồng này chỉ chạy khi CẢ 3 lời gọi đều đã thành công
console.log(`Đã tải ${posts.length} bài viết`);
console.log(`Đã tải ${comments.length} bình luận`);
console.timeEnd("FetchData"); // Thời gian chờ chỉ bằng thời gian của API chậm nhất
})
.catch(error => {
// Nếu có DÙ CHỈ 1 Promise thất bại, toàn bộ Promise.all sẽ thất bại
console.error("Một trong các API bị lỗi:", error);
});
Mối Liên Hệ Giữa Promise Và Async/Await
Dù async/await là cú pháp hiện đại được ưa chuộng nhất hiện nay, bản chất bên dưới của nó vẫn là Promise. Từ khóa await thực chất là một cách viết đẹp hơn (syntactic sugar) để "bóc" kết quả từ một Promise mà không cần dùng hàm .then(). Hiểu rõ cơ chế hoạt động, các trạng thái của Promise và cách nối chuỗi chính là nền tảng bắt buộc trước khi bạn sử dụng thành thạo async/await trong các dự án JavaScript thực tế.
Lập trình bất đồng bộ (asynchronous) là trái tim của JavaScript, giúp các ứng dụng web gọi API, đọc tệp tin hay truy vấn cơ sở dữ liệu mà không làm "treo" trình duyệt của người dùng. Trước đây, lập trình viên phải vật lộn với các hàm callback lồng nhau chằng chịt (Callback Hell). Sự ra đời của Promise trong JavaScript đã thay đổi hoàn toàn cục diện, mang lại cách viết code gọn gàng, dễ quản lý và chuyên nghiệp hơn.
Promise Trong JavaScript Là Gì?
Promise là một đối tượng đại diện cho sự hoàn thành (hoặc thất bại) của một thao tác bất đồng bộ và kết quả của nó trong tương lai.
3 Trạng Thái Cốt Lõi Của Một Promise
Một Promise trong JavaScript luôn tồn tại ở một trong ba trạng thái (states) sau:
Pending (Đang chờ): Trạng thái ban đầu, thao tác chưa hoàn thành cũng chưa thất bại (đang pha trà sữa).
Fulfilled (Thành công): Thao tác đã hoàn thành thành công và trả về một kết quả (nhận được ly trà sữa).
Rejected (Thất bại): Thao tác thất bại và trả về một lý do hoặc lỗi (quán thông báo hết nguyên liệu).
Khi một Promise chuyển từ trạng thái Pending sang Fulfilled hoặc Rejected, nó được coi là đã Settled (đã giải quyết xong) và không thể thay đổi trạng thái được nữa.
Cách Khởi Tạo Và Sử Dụng Promise Cơ Bản
Để tạo ra một Promise mới, chúng ta sử dụng từ khóa new Promise truyền vào một hàm thực thi (executor function) với hai tham số resolve và reject.
Sau khi có Promise, chúng ta xử lý kết quả trả về bằng các phương thức .then() và .catch():
Giải Quyết Callback Hell Bằng Promise Chaining
Điểm mạnh lớn nhất của cách sử dụng Promise là khả năng nối chuỗi (Chaining). Mỗi phương thức .then() lại trả về một Promise mới, cho phép bạn thực hiện các thao tác tuần tự một cách vô cùng sạch sẽ.
Nếu có bất kỳ lỗi nào xảy ra ở bất kỳ mắt xích .then() nào, luồng thực thi sẽ nhảy thẳng xuống block .catch() duy nhất ở cuối cùng để xử lý.
Tăng Tốc Độ Trải Nghiệm Với Promise.all
Trong thực tế ứng dụng, bạn thường cần gọi nhiều API không liên quan đến nhau. Thay vì chờ cái này xong mới gọi cái kia (tuần tự), bạn có thể dùng Promise.all() để gọi tất cả cùng lúc (song song), giúp giảm đáng kể thời gian chờ.
Mối Liên Hệ Giữa Promise Và Async/Await
Dù async/await là cú pháp hiện đại được ưa chuộng nhất hiện nay, bản chất bên dưới của nó vẫn là Promise. Từ khóa await thực chất là một cách viết đẹp hơn (syntactic sugar) để "bóc" kết quả từ một Promise mà không cần dùng hàm .then(). Hiểu rõ cơ chế hoạt động, các trạng thái của Promise và cách nối chuỗi chính là nền tảng bắt buộc trước khi bạn sử dụng thành thạo async/await trong các dự án JavaScript thực tế.