Rate this post

Chào bạn, nếu bạn đã có kinh nghiệm lập trình với JavaScript, chắc hẳn bạn đã gặp phải khái niệm hoặc sử dụng callback. Trong quá trình phát triển JavaScript, một vấn đề mà nhiều lập trình viên gặp phải là “callback hell” hay còn được gọi là “địa ngục callback.” Dưới đây là một ví dụ minh họa về tình trạng này:

asyncFunc1(function() {
    asyncFunc2(function() {
        asyncFunc3(function() {
            // Và cứ tiếp tục như vậy...
        });
    });
});

Để giải quyết vấn đề này, Promise đã xuất hiện như một lời giải hứa hẹn. Trong bài viết này, mình sẽ giới thiệu về Promise và cách nó giúp chúng ta vượt qua “callback hell” này 😄. Đây thực sự là một tin vui cho các nhà phát triển JavaScript, giống như một món quà Giáng Sinh. Việc Promise trở thành một phần của chuẩn JavaScript là một bước quan trọng. Ngay cả Chrome 32 phiên bản beta đã tích hợp các API Promise cơ bản. Mặc dù ý tưởng của Promise không mới với các nhà phát triển, với việc sử dụng trong một số thư viện như Q, when, RSVP, Bluebird,… thậm chí cả jQuery cũng có một chức năng gọi là Deferred Object tương đương với Promise. Tuy nhiên, việc Promise được hỗ trợ nguyên bản thực sự là một điều đáng kinh ngạc. Bài viết dưới đây sẽ trình bày những điểm cơ bản của Promise và cách nó có thể nâng cao chất lượng mã nguồn JavaScript của bạn 😄.

Promise là gì ?

Một đối tượng Promise trong JavaScript đại diện cho một giá trị tại thời điểm hiện tại, có thể chưa tồn tại, nhưng sẽ được xử lý và có giá trị vào một điểm trong tương lai. Việc này giúp làm cho mã nguồn xử lý không đồng bộ trở nên giống như là đồng bộ hơn. Ví dụ, khi sử dụng Promise để gọi API và nhận dữ liệu, bạn tạo ra một đối tượng Promise đại diện cho dữ liệu sẽ được lấy từ API. Quan trọng ở đây là dữ liệu chưa tồn tại khi đối tượng Promise được tạo ra, mà chỉ trở nên truy cập được khi có phản hồi từ dịch vụ web. Trong quá trình chờ đợi dữ liệu, đối tượng Promise sẽ đóng vai trò như một “proxy” cho dữ liệu. Hơn nữa, bạn có thể đính kèm các callback vào đối tượng Promise để thực hiện xử lý dữ liệu. Những callback này chỉ được thực hiện khi dữ liệu đã sẵn sàng.

Ví dụ:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const data = getDataFromServer();
    if (data) {
      resolve(data);
    } else {
      reject(new Error('Error fetching data'));
    }
  }, 2000);
});

promise
  .then(data => console.log(data))
  .catch(error => console.error(error));

Trong ví dụ trên, chúng ta sử dụng hàm setTimeout để chờ cho dữ liệu từ một server về, khi dữ liệu được trả về chúng ta sử dụng resolve(data) để gửi kết quả về cho hàm then và nếu có lỗi chúng ta sử dụng reject(new Error(‘Error fetching data’))

Promise API

Tiếp theo, chúng ta sẽ tìm hiểu về các API cơ bản của Promise. Để bắt đầu, hãy xem đoạn mã sau, đây là cách khởi tạo một đối tượng Promise:

if (window.Promise) { // Kiểm tra xem trình duyệt có hỗ trợ Promise không
  var promise = new Promise(function(resolve, reject) {
    // Các đoạn mã bất đồng bộ sẽ nằm ở đây
  });
}

Chúng ta bắt đầu bằng cách khởi tạo một đối tượng Promise và truyền vào một hàm callback. Hàm callback này nhận hai tham số là resolvereject, cả hai đều là các hàm. Mọi đoạn mã bất đồng bộ sẽ được đặt trong hàm callback này. Nếu mọi thứ thành công, Promise sẽ hoàn thành và hàm resolve sẽ được gọi. Trong trường hợp có lỗi, hàm reject sẽ được gọi với một đối tượng Error, xác định rằng Promise đã thất bại.

Bây giờ, hãy xây dựng một ví dụ đơn giản để mô tả cách sử dụng Promise. Dòng mã sau đây sẽ tạo một yêu cầu bất đồng bộ đến một dịch vụ web và trả về một đoạn JSON:

if (window.Promise) {
  console.log('Promise found');

  var promise = new Promise(function(resolve, reject) {
    var request = new XMLHttpRequest();

    request.open('GET', 'http://api.icndb.com/jokes/random');
    request.onload = function() {
      if (request.status == 200) {
        resolve(request.response); // chúng ta đã nhận được dữ liệu, nên resolve Promise
      } else {
        reject(Error(request.statusText)); // trạng thái không phải là 200 OK, nên reject
      }
    };

    request.onerror = function() {
      reject(Error('Error fetching data.')); // lỗi xảy ra, nên reject Promise
    };

    request.send(); // gửi yêu cầu
  });

  console.log('Yêu cầu bất đồng bộ đã được thực hiện.');

  promise.then(function(data) {
    console.log('Nhận được dữ liệu! Promise đã được hoàn thành.');
    document.getElementsByTagName('body')[0].textContent = JSON.parse(data).value.joke;
  }, function(error) {
    console.log('Promise bị từ chối.');
    console.log(error.message);
  });
} else {
  console.log('Promise không khả dụng');
}

Trong đoạn mã trên, phần khởi tạo Promise chứa mã bất đồng bộ được sử dụng để lấy dữ liệu từ dịch vụ web thông qua API. Ở đây, chúng ta tạo một yêu cầu Ajax đến liên kết http://api.icndb.com/jokes/random, nơi sẽ trả về một câu chuyện cười ngẫu nhiên. Khi có phản hồi (chuỗi JSON), nội dung của phản hồi sẽ được truyền và xử lý bởi hàm resolve(). Nếu có lỗi, hàm reject() sẽ được gọi với thông điệp lỗi.

Khi chúng ta khởi tạo một đối tượng Promise, chúng ta có một proxy đến dữ liệu sẽ sẵn sàng sử dụng trong tương lai. Trong trường hợp cụ thể này, chúng ta hy vọng rằng dữ liệu sẽ được truyền về từ dịch vụ web vào một thời điểm nào đó. Để biết khi nào dữ liệu sẽ sẵn sàng, Promise.then() sẽ giải quyết vấn đề này cho chúng ta. Hàm then sẽ nhận hai tham số với ý n

ghĩa: một callback thành công và một callback thất bại. Những callback này sẽ được gọi khi Promise được xử lý xong (thành công hoặc thất bại). Nếu Promise được xử lý trơn tru, callback thành công sẽ được gọi với dữ liệu truyền vào hàm resolve(). Nếu promise gặp lỗi, callback thất bại sẽ được gọi. Bất kể thứ gì bạn truyền vào reject() sẽ được truyền như một tham số đến callback này.

Đồng thời, hãy bật console của trình duyệt để theo dõi thứ tự thực hiện code. Lưu ý rằng mỗi promise sẽ có 3 trạng thái:

  • Đang xử lý (Pending)
  • Đã hoàn thành (Fulfilled)
  • Đã bị từ chối (Rejected)

Mỗi đối tượng Promise sẽ có một thuộc tính riêng chứa trạng thái hiện tại của Promise. Khi một Promise được hoàn thành hoặc bị từ chối, giá trị của thuộc tính này sẽ ngay lập tức được cập nhật với trạng thái của Promise. Điều này có nghĩa là một Promise chỉ có thể thành công hoặc thất bại một lần duy nhất. Nếu một Promise đã được hoàn thành, và sau đó bạn gọi then() của promise và truyền vào 2 callback, thì callback thành công sẽ luôn được gọi. Vì vậy, trong thế giới của Promise, chúng ta không quan trọng việc Promise được xử lý khi nào. Chúng ta chỉ quan trọng đến kết quả đầu ra của Promise.

Nối Nhiều Promise

Trong nhiều trường hợp, chúng ta muốn nối nhiều Promise với nhau. Ví dụ, khi có nhiều thao tác bất đồng bộ cần xử lý. Khi một thao tác trả về dữ liệu, chúng ta có thể bắt đầu một xử lý bất đồng bộ khác sử dụng một phần dữ liệu từ thao tác trước đó và tiếp tục như vậy. Promise hỗ trợ việc nối các Promise với nhau, như ví dụ dưới đây:

function getPromise(url) {
  // Trả về một Promise ở đây
  // Gửi một request để lấy dữ liệu từ một URL (request bất đồng bộ)
  // Sau khi nhận được kết quả, xử lý Promise với dữ liệu nhận được
}

var promise = getPromise('some url here');

promise.then(function(result) {
  // Chúng ta có dữ liệu của URL 'some url here' ở đây
  return getPromise(result); // Và trả về một Promise khác
}).then(function(result) {
  // Ở đây chứa kết quả Promise vừa trả về ở trên và logic để xử lý dữ liệu cuối cùng
});

Một điều quan trọng là khi một giá trị thông thường được trả về trong hàm then() đầu tiên, hàm then() thứ hai sẽ thực hiện với giá trị đó. Tuy nhiên, nếu bạn trả về một Promise trong hàm then() đầu tiên, thì hàm then() thứ hai sẽ chờ và chỉ được gọi khi Promise trong hàm then() đầu tiên đã hoàn thành.

Xử Lý Lỗi

Trước đó, tôi đã giới thiệu rằng hàm then() sẽ nhận 2 hàm callback làm tham số. Hàm callback thứ hai sẽ được gọi khi Promise bị từ chối. Tuy nhiên, chúng ta cũng có một cách khác, sử dụng một hàm tên là catch(). Chúng ta có thể xử lý khi Promise bị từ chối bằng đoạn mã dưới đây:

promise.then(function(result) {
  console.log('Got data!', result);
}).catch(function(error) {
  console.log('Error occurred!', error);
});

Đoạn mã trên tương đương với:

promise.then(function(result) {
  console.log('Got data!', result);
}).then(undefined, function(error) {
  console.log('Error occurred!', error);
});

Lưu ý rằng nếu Promise bị từ chối và hàm then() không có callback để xử lý việc này, xử lý sẽ được chuyển đến hàm xử lý lỗi của hàm then() tiếp theo hoặc hàm catch() tiếp theo. Ngoài việc được sử dụng để xử lý Promise bị lỗi, hàm callback trong catch() cũng được gọi khi có bất kỳ exception nào được bắn ra từ callback của hàm khởi tạo Promise. Do đó, bạn có thể sử dụng catch() để lưu log. Chú ý rằng chúng ta có thể sử dụng try...catch để xử lý lỗi, nhưng điều đó là không cần thiết khi sử dụng Promise vì bất kỳ exception nào cũng luôn được xử lý bởi hàm catch().**

Tại sao phải sử dụng promise trong javascript ? 

Promise được sử dụng trong JavaScript vì nó cung cấp một cách dễ dàng và rõ ràng hơn để xử lý các tác vụ bất đồng bộ.

Trước khi sử dụng Promise, chúng ta thường sử dụng callback function để xử lý tác vụ bất đồng bộ. Tuy nhiên, sử dụng callback function có thể gây làm cho mã trở nên khó đọc và khó hiểu khi chúng ta cần chờ cho nhiều hàm khác nhau hoàn thành. Điều này được gọi là “Callback Hell”

Promise giải quyết vấn đề này bằng cách cho phép chúng ta chờ cho một tác vụ bất đồng bộ hoàn thành và xử lý kết quả trả về bằng cách sử dụng .then() và .catch(). Nó còn cung cấp một cách để chạy nhiều tác vụ bất đồng bộ song song và chờ cho chúng hoàn thành trước khi tiếp tục thực thi mã tiếp theo.

Promise được khởi tạo với một hàm xử lý, trong đó chúng ta có thể chỉ định hành động cần thực hiện khi promise được resolve hoặc reject. Khi promise được resolve, chúng ta có thể sử dụng .then() để xử lý kết quả. Khi promise bị reject, chúng ta có thể sử dụng .catch() để xử lý lỗi. Nó cho phép chúng ta chờ đồng bộ hoàn thành các tác vụ bất đồng bộ và xử lý kết quả trả về.

Ví dụ :

//Khởi tạo một promise
const myPromise = new Promise((resolve, reject) => {
  // thực hiện một tác vụ bất đồng bộ
  setTimeout(() => {
    resolve('Success');
  }, 1000);
});

//Sử dụng .then() và .catch() để xử lý kết quả
myPromise
  .then((data) => {
    console.log(data); // Success
  })
  .catch((error) => {
    console.log(error);
  });

Promise còn cung cấp một cách để giải quyết vấn đề liên quan đến việc chờ cho một tác vụ bất đồng bộ hoàn thành và làm cho mã bất đồng bộ trở nên dễ đọc và dễ hiểu hơn, giúp các nhà phát triển giảm thiểu tình trạng “Callback Hell”.

Sử dụng promise trong javascript như thế nào ? 

Để sử dụng Promise trong JavaScript, bạn cần tạo ra một đối tượng Promise với một hàm xử lý tác vụ bất đồng bộ. Hàm này có hai tham số: resolve và reject.

resolve được gọi khi tác vụ bất đồng bộ hoàn thành và trả về kết quả. reject được gọi khi có lỗi xảy ra trong quá trình thực hiện tác vụ.

Ví dụ:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const data = getDataFromServer();
    if (data) {
      resolve(data);
    } else {
      reject(new Error('Error fetching data'));
    }
  }, 2000);
});

Sau khi tạo ra đối tượng Promise, bạn có thể sử dụng phương thức .then() để xử lý kết quả trả về từ hàm resolve và .catch() để xử lý lỗi từ hàm reject.

promise
  .then(data => console.log(data))
  .catch(error => console.error(error));

Promise còn có một số phương thức khác như .finally() dùng để xử lý sự kiện khi tác vụ hoàn thành hoặc có lỗi.

Còn Promise.all() dùng để chạy nhiều promise song song và chờ cho tất cả hoàn thành trước khi tiếp tục thực thi mã tiếp theo.

Một số ví dụ cụ thể về promise trong javascript 

  1. Lấy dữ liệu từ một API:
const getData = url => {
  return new Promise((resolve, reject) => {
    fetch(url)
      .then(response => response.json())
      .then(data => resolve(data))
      .catch(error => reject(error));
  });
};

getData('https://some-api.com/data')
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.log(error);
  });

  1. Tải một ảnh từ internet
const downloadImage = url => {
  return new Promise((resolve, reject) => {
    const image = new Image();
    image.src = url;
    image.onload = () => resolve(image);
    image.onerror = error => reject(error);
  });
};

downloadImage('https://some-website.com/images/logo.png')
  .then(image =>{
  ///
})

Kết Luận

Trên đây chỉ là một sơ lược về API Promise trong Javascript. Khi bạn hiểu rõ về nó, việc viết mã xử lý các thao tác bất đồng bộ sẽ trở nên dễ dàng hơn. Bạn có thể thực hiện xử lý mà không cần lo lắng về giá trị sẽ được trả về trong tương lai. Hơn nữa, còn một số API khác liên quan đến Promise mà chưa được đề cập ở đây. Điều này mở ra nhiều cơ hội để tối ưu hóa và mở rộng khả năng sử dụng của Promise trong quá trình phát triển ứng dụng của bạn.

Trả lời

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 *

Contact Me on Zalo
Call now