Skip to content

Lập trình/ Code

Promise là gì? Giải mã “Promise” trong JavaScript từ A-Z

Bạn đang vật lộn với Callback Hell? Bài viết này sẽ giải thích chi tiết Promise là gì, 3 trạng thái, cách sử dụng .then, .catch, .finally và cú pháp async/await để xử lý bất đồng bộ trong JavaScript một cách hiệu quả. Trong thế giới lập trình JavaScript hiện đại, việc xử lý các […]

Văn Tân
Văn Tân Tác giả
11 phút đọc 2,084 từ 474 lượt xem
Promise là gì? Giải mã “Promise” trong JavaScript từ A-Z
Mục lục bài viết (8 mục)
Có thể bạn biết?

Bạn đang vật lộn với Callback Hell? Bài viết này sẽ giải thích chi tiết Promise là gì, 3 trạng thái, cách sử dụng .then, .catch, .finally và cú pháp async/await để xử lý bất đồng bộ trong JavaScript một cách hiệu quả.

Bạn đang vật lộn với Callback Hell? Bài viết này sẽ giải thích chi tiết Promise là gì, 3 trạng thái, cách sử dụng .then, .catch, .finally và cú pháp async/await để xử lý bất đồng bộ trong JavaScript một cách hiệu quả.

Trong thế giới lập trình JavaScript hiện đại, việc xử lý các tác vụ bất đồng bộ (asynchronous) là điều không thể tránh khỏi, từ việc gọi API, đọc file cho đến các thao tác cần thời gian phản hồi. Trước đây, chúng ta thường dùng Callback, nhưng nó nhanh chóng dẫn đến một “cơn ác mộng” mang tên Callback Hell. Và Promise ra đời như một vị cứu tinh, mang đến một cách viết code sạch sẽ, dễ đọc và dễ quản lý hơn.

Vậy Promise là gì? Làm thế nào để sử dụng nó một cách hiệu quả? Hãy cùng khám phá tất tần tật trong bài viết này nhé!

1. Promise là gì? Một ví dụ dễ hiểu

Hãy tưởng tượng bạn đặt hàng online (ví dụ: gọi một API để lấy dữ liệu).

Promise (lời hứa) chính là một đối tượng đại diện cho một “lời hứa” từ người bán hàng (API server). “Lời hứa” này có nội dung: “Tôi đã nhận được yêu cầu của bạn. Tôi hứa sẽ xử lý và trả về kết quả trong tương lai. Kết quả đó có thể là hàng đã được giao thành công hoặc đơn hàng bị hủy vì hết hàng.”

Promise là gì
Promise là gì

Trong JavaScript, Promise là một đối tượng (object) đại diện cho sự hoàn thành hoặc thất bại (trong tương lai) của một tác vụ bất đồng bộ.

Một Promise sẽ có 3 trạng thái:

  1. pending: Trạng thái ban đầu, “lời hứa” đang được xử lý (chờ giao hàng).
  2. fulfilled: Trạng thái hoàn thành, tác vụ đã thành công và trả về một giá trị (hàng được giao thành công).
  3. rejected: Trạng thái thất bại, tác vụ đã gặp lỗi và trả về một lý do (đơn hàng bị hủy).

Một khi Promise đã chuyển từ pending sang fulfilled hoặc rejected, trạng thái của nó sẽ không bao giờ thay đổi nữa.

2. Tại sao phải dùng Promise? Cứu tinh của “Callback Hell”

Để hiểu rõ sức mạnh của Promise, hãy xem xét ví dụ về “Callback Hell”. Giả sử bạn cần thực hiện 3 tác vụ bất đồng bộ nối tiếp nhau: lấy thông tin người dùng, sau đó lấy danh sách bài viết của người đó, rồi lấy tiếp bình luận của bài viết đầu tiên.

Tại sao phải dùng Promise
Tại sao phải dùng Promise

Nếu dùng Callback:

JavaScript
getUser(1, (user) => {
  console.log('Lấy được user:', user);
  getPosts(user.id, (posts) => {
    console.log('Lấy được posts:', posts);
    getComments(posts[0].id, (comments) => {
      console.log('Lấy được comments:', comments);
      // Và nếu có thêm tác vụ nữa... code sẽ càng thụt vào trong
    });
  });
});

Đoạn code trên có cấu trúc “kim tự tháp ngược”, rất khó đọc, khó bảo trì và khó xử lý lỗi. Đây chính là Callback Hell.

Khi Promise xuất hiện:

JavaScript
getUser(1)
  .then(user => {
    console.log('Lấy được user:', user);
    return getPosts(user.id); // Trả về một Promise khác
  })
  .then(posts => {
    console.log('Lấy được posts:', posts);
    return getComments(posts[0].id); // Lại trả về một Promise khác
  })
  .then(comments => {
    console.log('Lấy được comments:', comments);
  })
  .catch(error => {
    console.error('Đã có lỗi xảy ra:', error); // Bắt lỗi cho cả chuỗi
  });

Rõ ràng, code sử dụng Promise trông phẳng hơn, dễ đọc theo trình tự từ trên xuống dưới và việc xử lý lỗi cũng tập trung ở một nơi duy nhất với .catch().

Xem thêm:

3. Cách tạo và sử dụng một Promise

Cú pháp khởi tạo

Chúng ta tạo một Promise bằng constructor new Promise. Nó nhận vào một hàm (executor) với hai tham số là resolve và reject.

  • resolve(value): Hàm được gọi khi tác vụ thành công, trả về giá trị value.
  • reject(error): Hàm được gọi khi tác vụ thất bại, trả về lý do error.
Cách tạo và sử dụng một Promise
Cách tạo và sử dụng một Promise
JavaScript
const myPromise = new Promise((resolve, reject) => {
  const isSuccess = true; // Giả sử tác vụ thành công

  setTimeout(() => {
    if (isSuccess) {
      resolve("Dữ liệu đã được xử lý thành công!"); // Chuyển sang trạng thái fulfilled
    } else {
      reject("Đã có lỗi xảy ra trong quá trình xử lý."); // Chuyển sang trạng thái rejected
    }
  }, 2000); // Giả lập một tác vụ tốn 2 giây
});

Sử dụng Promise

Để “nhận” kết quả từ Promise, chúng ta sử dụng các phương thức .then(), .catch(), và .finally().

.then(onFulfilled, onRejected)

Phương thức này nhận vào 2 hàm callback:

  • onFulfilled: Được thực thi khi Promise ở trạng thái fulfilled.
  • onRejected: (Tùy chọn) Được thực thi khi Promise ở trạng thái rejected.
JavaScript
myPromise.then(
  (successMessage) => {
    // onFulfilled
    console.log("Thành công:", successMessage);
  },
  (errorMessage) => {
    // onRejected
    console.error("Thất bại:", errorMessage);
  }
);

.catch(onRejected)

Đây là cú pháp “ngọt ngào” hơn, chuyên dùng để bắt lỗi (thay cho việc truyền hàm thứ hai vào .then()). Đây là cách được khuyến khích sử dụng.

JavaScript
myPromise
  .then((successMessage) => {
    console.log("Thành công:", successMessage);
  })
  .catch((errorMessage) => {
    console.error("Thất bại:", errorMessage);
  });

.finally(onFinally)

Hàm callback bên trong .finally() sẽ luôn được thực thi dù Promise thành công hay thất bại. Nó rất hữu ích cho các tác vụ dọn dẹp như tắt loading spinner.

JavaScript
showLoadingSpinner(); // Hiện loading

myPromise
  .then((data) => console.log(data))
  .catch((error) => console.error(error))
  .finally(() => {
    hideLoadingSpinner(); // Luôn ẩn loading dù thành công hay thất bại
  });

4. Các phương thức tĩnh hữu ích của Promise

Ngoài các phương thức trên, đối tượng Promise còn cung cấp các phương thức tĩnh mạnh mẽ.

  • Promise.all(iterable): Nhận vào một mảng các Promise và trả về một Promise mới. Promise mới này sẽ fulfilled khi tất cả Promise trong mảng đều fulfilled, và sẽ rejected ngay khi có bất kỳ Promise nào trong mảng rejected. Rất hữu ích khi bạn cần thực hiện nhiều tác vụ bất đồng bộ song song.
    JavaScript
    Promise.all([
      fetch('api/users/1'),
      fetch('api/products/1')
    ])
    .then(([userResponse, productResponse]) => {
      // Xử lý khi cả hai API đều gọi thành công
    })
    .catch(error => console.error('Một trong các API đã thất bại', error));
    
  • Promise.race(iterable): Nhận vào một mảng các Promise và trả về một Promise mới. Promise mới này sẽ được giải quyết (fulfilled hoặc rejected) ngay khi Promise đầu tiên trong mảng được giải quyết. Giống như một cuộc đua, ai về đích trước sẽ quyết định kết quả.
    JavaScript

    const p1 = new Promise(resolve => setTimeout(resolve, 500, 'one'));
    const p2 = new Promise(resolve => setTimeout(resolve, 100, 'two'));
    
    Promise.race([p1, p2]).then(value => {
      console.log(value); // "two" - vì p2 hoàn thành trước
    });
    

5. Từ Promise đến Async/Await: Một cú pháp thanh lịch hơn

ES7 (ECMAScript 2017) đã giới thiệu async/await, một cú pháp giúp làm việc với Promise trở nên dễ dàng và trực quan hơn nữa. Về bản chất, nó chỉ là “syntactic sugar” (cú pháp thay thế) cho Promise, giúp code trông giống như code đồng bộ thông thường.

  • async: Đặt trước một hàm để cho biết hàm đó luôn trả về một Promise.
  • await: Chỉ có thể được sử dụng bên trong một hàm async. Nó sẽ tạm dừng việc thực thi hàm, chờ cho đến khi Promise được giải quyết, và sau đó trả về kết quả.

Hãy viết lại ví dụ “Callback Hell” ở trên bằng async/await:

JavaScript

async function fetchUserData() {
  try {
    const user = await getUser(1);
    console.log('Lấy được user:', user);

    const posts = await getPosts(user.id);
    console.log('Lấy được posts:', posts);

    const comments = await getComments(posts[0].id);
    console.log('Lấy được comments:', comments);
  } catch (error) {
    console.error('Đã có lỗi xảy ra:', error); // Xử lý lỗi bằng try...catch
  }
}

fetchUserData();

Bạn có thể thấy, code với async/await cực kỳ sạch sẽ, dễ đọc và dễ hiểu, giống hệt như cách chúng ta viết code đồng bộ tuần tự. Việc xử lý lỗi được thực hiện bằng khối try...catch quen thuộc.

Kết luận

Promise không phải là một khái niệm quá phức tạp. Nó là một “lời hứa” về một giá trị sẽ có trong tương lai, giúp chúng ta thoát khỏi “Callback Hell” và quản lý code bất đồng bộ một cách mạch lạc. Bằng việc nắm vững cách tạo Promise, sử dụng .then(), .catch(), .finally(), và đặc biệt là áp dụng cú pháp hiện đại async/await, bạn đã có trong tay một công cụ vô cùng mạnh mẽ để xây dựng các ứng dụng JavaScript hiệu quả và chuyên nghiệp.

Chúc bạn thành công trên con đường chinh phục JavaScript!

Chia sẻ:
Văn Tân
Về Tác Giả ✓ Verified

Văn Tân

Chào bạn, mình là Văn Tân hay còn gọi là Tân Digi - Người sáng lập ra TaSDigital, Tân rất đam mê về công nghệ và thích viết…

Tất cả bài viết

Bình luận (0)

Viết bình luận của bạn

Đánh dấu * là bắt buộc

Báo cáo vi phạm

Báo cáo nội dung

Nếu bạn phát hiện nội dung sai lệch, vi phạm bản quyền hoặc cần cập nhật, vui lòng gửi thông báo cho chúng tôi.

Trợ lý AI TAS Digital
Câu hỏi không được lưu trữ — Chính sách bảo mật

Trợ lý AI TAS Digital — trả lời tức thì 24/7