Làm Việc Với Promises Trong JavaScript

Chào các bạn! Ở bài học trước chúng ta đã tìm hiểu về callback trong JavaScript. Tiếp tục với series hôm nay chúng ta sẽ cùng nhau làm rõ các vấn đề về promises thông qua các ví dụ thực tiễn.

Promises trong JavaScript là gì?

Promise là một cơ trong JavaScript giúp bạn thực thi các tác vụ bất đồng bộ mà không rơi vào  tình trạng có quá nhiều các callback lồng vào nhau gây tình trạng callback hell hay pyramid of doom. Thực tế promises là 1 special JavaScript object thể hiện cho sự hoàn thành hoặc thất bại của một tiến trình bất đồng bộ.

Cách sử dụng promises trong JavaScript

Cú pháp:

let promise = new Promise(function(resolve, reject) {
});

Khi promise thực thi hoàn thành công việc, nó sẽ gọi một trong 2 hàm là resolve(value)reject(error). Giả sử trên kia là một lời cầu hôn của chàng trai dành cho cô gái, ban đầu cô gái hẳn sẽ ngại ngùng còn ngập ngừng (Pending), nhưng sau đó chắc chắn cô gái sẽ đồng ý hoặc từ chối bạn. Hai đối số trên cũng giống như lời tỏ tình vậy. Nếu đồng ý resolve(value) thì status sẽ là "fulfilled", và result chính là value. Còn cô gái từ chối reject(error) lúc này status sẽ là "rejected" và result chình là error.

Để tiếp tục câu chuyện tình yêu ban nãy, bây giờ chúng ta sẽ khởi tạo một promises và thực thi thông qua setTimeout.

let promise = new Promise(function(resolve, reject) {
  // the function is executed automatically when the promise is constructed

  // after 1 second signal that the job is done with the result "done"
  setTimeout(() => resolve("done"), 1000);
});

Đây chính là trường hợp chàng trai đã đưa ra lời tỏ tình thành công với cô gái, đó hẳn một tin vui. Tuy nhiên cô gái vẫn chưa đưa ra câu trả lời. Ở đoạn code trên, sau một giây “xử lý”, trình thực thi sẽ gọi resolve("done")để đưa ra kết quả. Điều này thay đổi trạng thái của promise đối tượng:

Còn nếu chàng trai cũng ngại ngùng nói không nên lời cầu hôn thì sao. Đó chắc chắn là thất bại rồi:

let promise = new Promise(function(resolve, reject) {
  // after 1 second signal that the job is finished with an error
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

Tóm lại, người thực thi nên thực hiện một công việc sau đó gọi resolve hoặc reject để thay đổi trạng thái của promise tương ứng. Một promise được giải quyết resolve hoặc bị từ chối reject được gọi là "settled", nó trái ngược với promise ban đầu "Pending".

then

Giả sử anh chàng ban nãy đã đưa ra lời tỏ tình thành công với cô gái. Vậy làm sao để chúng ta biết cô gái đó đồng ý hay không. Lúc này giải pháp được đưa ra là sử dụng then. Nó có cú pháp cơ bản như sau:

promise.then(
  function(result) { /* handle a successful result */ },
  function(error) { /* handle an error */ }
);
  • Đối số đầu tiên của .then là một hàm chạy khi promise được giải quyết và nhận kết quả.
  • Đối số thứ hai của .then là một hàm chạy khi promise bị từ chối và nhận được lỗi.

Ví dụ: đây là một promise được giải quyết thành công:

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve("done!"), 1000);
});

// resolve runs the first function in .then
promise.then(
  result => alert(result), // shows "done!" after 1 second
  error => alert(error) // doesn't run
);

Còn đây là trường hợp bị từ chối:

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

// reject runs the second function in .then
promise.then(
  result => alert(result), // doesn't run
  error => alert(error) // shows "Error: Whoops!" after 1 second
);

Còn nếu bạn muốn cô gái đồng ý luôn thì:

let promise = new Promise(resolve => {
  setTimeout(() => resolve("done!"), 1000);
});

promise.then(alert); // shows "done!" after 1 second

Đây là trường hợp bạn chỉ quan tâm đến việc hoàn thành thành công, bạn có thể cung cấp một đối số hàm cho .then

catch

Nếu bạn chỉ quan tâm đến sai sót, bạn có thể sử dụng  null như là đối số đầu tiên: .then(null, errorHandlingFunction). Hoặc chúng ta có thể sử dụng .catch(errorHandlingFunction)

let promise = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

// .catch(f) is the same as promise.then(null, f)
promise.catch(alert); // shows "Error: Whoops!" after 1 second

finally

finally là một trình xử lý tốt để thực hiện việc dọn dẹp, ví dụ như dừng các loading indicators, vì chúng không còn cần thiết nữa, bất kể kết quả là gì.

Như thế này:

new Promise((resolve, reject) => {
  /* do something that takes time, and then call resolve/reject */
})
  // runs when the promise is settled, doesn't matter successfully or not
  .finally(() => stop loading indicator)
  // so the loading indicator is always stopped before we process the result/error
  .then(result => show result, err => show error)
  • Một finallytrình xử lý không có đối số. Trong finallychúng ta không biết liệu promise có thành công hay không.
  • Một finallytrình xử lý chuyển kết quả và lỗi cho trình xử lý tiếp theo.

Ví dụ, ở đây kết quả được chuyển từ finally đến then:

new Promise((resolve, reject) => {
  setTimeout(() => resolve("result"), 2000)
})
  .finally(() => alert("Promise ready"))
  .then(result => alert(result)); // <-- .then handles the result

Còn ở đây có một lỗi trong promise, nó sẽ chuyển từ finally qua catch:

new Promise((resolve, reject) => {
  throw new Error("error");
})
  .finally(() => alert("Promise ready"))
  .catch(err => alert(err));  // <-- .catch handles the error object

Ở bài trước chúng ta đã cùng nhau xây dựng hàm loadScript để tải 1 tệp. Bây giờ chúng ta sẽ cùng nhau viết lại nó bằng promise

function loadScript(src) {
  return new Promise(function(resolve, reject) {
    let script = document.createElement('script');
    script.src = src;

    script.onload = () => resolve(script);
    script.onerror = () => reject(new Error(`Script load error for ${src}`));

    document.head.append(script);
  }
);
}

Kết hợp với then ta được:

let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js");

promise.then(
  script => alert(`${script.src} is loaded!`),
  error => alert(`Error: ${error.message}`)
);

promise.then(script => alert('Another handler...'));

Vậy là bài học thứ 2 trong series về promises, async/await đã kết thúc. Hẹn gặp lại các bạn ở bài học thứ 3 promises chaining nhé!


Trần Quang Hào
SUNTECH VIỆT NAM   Đăng ký để nhận thông báo mới nhất