Mục lục

    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à resolvereject.

    • 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!

    5/5 - (1 bình chọn)

    Công nghệ tương lai Lập trình/ Code

    Portainer Là Gì? Toàn Tập Về Công Cụ Quản Trị Container Hàng Đầu (Hướng Dẫn Chi Tiết)

    Sự bùng nổ của công nghệ Container hóa (Containerization) với đầu tàu là Docker

    Xem thêm

    Công nghệ tương lai Công cụ và hướng dẫn Lập trình/ Code

    Helper Là Gì? Bí Quyết Viết Code “Sạch” Và Tối Ưu Trong Lập Trình

    Trong thế giới lập trình và phát triển phần mềm, việc phải lặp đi

    Xem thêm

    Digital Maketing Đồ Họa và Video Xu hướng

    Des là gì? Giải mã ý nghĩa của Des trong Thiết kế, SEO, IT & Logistics

    Bạn đang lướt mạng xã hội và thấy ai đó bình luận: “Dân Des

    Xem thêm

    Để lại một bình luận

    Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

    Chào mừng bạn đến với TASDIGITAL.net
    Chào mừng bạn đến với TASDIGITAL.net