Xử lý bất đồng bộ với Promise.all trong JavaScript

0

Anh em lập trình viên JavaScript chắc không xa lạ gì với Promise hay async/await trong việc xử lý các tác vụ bất đồng độ trong ứng dụng của mình; tuy vậy trong thực tế dự án có nhiều bài toán cần không chỉ 1 Promise và cần handle (xử lý) kết quả trả về trong từng Promise con, lúc này thứ chúng ta cần là Promise.all và 1 vài thứ tốt hơn thế. Bài viết này mình sẽ chia sẻ cùng các bạn cách sử dụng Promise.all trong những bài toán cụ thể ở dự án mình từng trải qua.

Nhắc lại cú pháp Promise

Trước hết cùng nhắc lại cú pháp Promise cũng như async/await cho các bạn đỡ quên nhé (lưu ý là async/await được thêm vào từ ES8 nhé).

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
);
async function f() {

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

  let result = await promise; // wait until the promise resolves (*)

  alert(result); // "done!"
}

2 ví dụ trên là cách sử dụng Promise hay async/await để thực hiện việc chờ 1 xử lý bất đồng bộ thực hiện xong, nhận kết quả trả về để thực hiện 1 hành động nào đó – cũng khá dễ hiểu đúng không. Vậy nếu bài toán của chúng ta là cần chờ 2 hành động bất đồng bộ (ví dụ như cần call 2 API lên server) thực hiện xong để lấy kết quả thì sẽ cần làm gì? JS cung cấp cho chúng ta Promise.all.

  Promise, Async/Await và Map/Reduce

  6 Lý do Async/Await của Javascript đánh bại Promises

Promise.all

Promise.all([Promise1, Promise2, Promise3])
  .then((result) => {
    console.log(result);
  })
  .catch((error) => console.log(`Error in promises ${error}`));

Promise1, Promise2 và Promise3 là những promise, bản thân Promise.all cũng là 1 promise. Vậy nó khác gì với việc chạy riêng lẻ 3 promise từ 1-3 một cách độc lập. Đầu tiên thì trong Promise.all, cả 3 Promise1-3 sẽ được thực hiện đồng thời, không cái nào cần chờ cái nào, vì thế giảm được thời gian chờ đợi và đương nhiên là tăng performance. Tiếp theo chúng ta cần lưu ý, nếu 1 trong 3 Promise1-3 đẩy ra lỗi (reject) thì Promise sẽ ngay lập tức dừng lại và nhảy vào catch chứ không thực hiện tiếp các promise khác. Trong trường hợp các task con thực hiện thành công thì Promise.all cũng sẽ trả về resolve với kết quả là 1 mảng chứa tất cả các resolve của task con.

Việc làm JavaScript Hồ Chí Minh dành cho bạn!

Đến đây chúng ta vẫn thấy quen thuộc đúng không, thực tế Promise.all được sử dụng thường xuyên trong các bài toán thực tế dự án. Mặc dù vậy sẽ có 1 số vấn đề khi sử dụng Promise.all như sau:

  • Làm sao để tạo 1 mảng Promise sử dụng map
  • Nếu muốn tất cả các Promise con thực hiện xong mà không bị dừng bị 1 trong các Promise khác thì làm sao?
  • Nếu muốn chỉ 1 trong các Promise con thực hiện xong (resolve) là hủy các Promise còn lại thì làm sao?
  • Nếu muốn chỉ 1 trong các Promise con trả về (bất kể resolve hay reject) là hủy các Promise còn lại thì làm sao?

Chúng ta cùng đi giải quyết các câu hỏi trên nhé.

Promise.all với map()

Một bài toán cũng hay gặp với Promise.all là việc sử dụng nó kết với với map nhằm tạo ra 1 group các xử lý bất đồng bộ theo mảng đầu vào. Chẳng hạn như bạn cần gửi email cho n users có sẵn trong 1 mảng dữ liệu; lúc này chúng ta có thể sử dụng hàm map() để tạo ra mảng promise con làm tham số đầu vào cho Promise.all. Ví dụ như dưới đây:

const timeOut = (t) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`Completed in ${t}`);
    }, t);
  });
};

const durations = [1000, 2000, 3000];

Promise.all(durations.map((duration) => timeOut(duration))).then((response) =>
  console.log(response)
);

Promise.allSettled

Promise.allSettled([
  Promise.reject("This failed."),
  Promise.reject("This failed too."),
  Promise.resolve("Ok I did it."),
  Promise.reject("Oopps, error is coming."),
]).then((res) => {
  console.log(`Here's the result: `, res);
});

Kết quả trả về:

[
  { status: "rejected", reason: "This failed." },
  { status: "rejected", reason: "This failed too." },
  { status: "fulfilled", value: "Ok I did it." },
  { status: "rejected", reason: "Oopps, error is coming." },
];

JavaScript cung cấp cho chúng ta Promise.allSettled để giải quyết vấn đề đầu tiên của Promise.all. Promise.allSettled trả về 1 Promise mới mà ở đó nó sẽ chờ tất cả các task con thực hiện xong, không kể có hoàn thành (resolve) hay bị reject. Kết quả trả về là 1 mảng theo thứ tự các Promise con truyền vào, mỗi object chứa status (trạng thái) của promise cùng giá trị trả về value hay reason (lí do) khi bị reject. Tất nhiên Promise.allSettled sẽ cần nhiều thời gian xử lý hơn so với Promise.all, tuy nhiên nó đảm bảo tất cả Promise được thực hiện.

Promise.race và Promise.any

Giả sử chúng ta có 2 bài toán như sau, các bạn hãy xem và lựa chọn các dùng nào của Promise nhé.

  • Bài toán 1: có 3 API get thông tin thời tiết và trả về kết quả như nhau, do vậy chỉ cần gọi 1 trong 3 API, cái nào có kết quả là được; không cần gọi tất cả 3 cái. Chúng ta sẽ làm thế nào?
  • Bài toán 2: chức năng upload cần upload file lên 3 server khác nhau và phải đảm bảo việc upload thực hiện thành công trên cả 3 server, nếu 1 trong 3 task fail thì dừng luôn 2 task còn lại.

Để giải quyết 2 bài toán trên, JavaScript cung cấp thêm cho chúng ta 2 function nữa là Promise.race và Promise.any. Promise.race và Promise.any có điểm chung là đều sẽ hoàn thành (resolve) khi 1 trong các promise con được thực hiện xong. Như ví dụ bài toán 1, nếu có 1 API get thông tin thời tiết hoàn thành xong thì nếu dùng Promise.race hoặc Promise.any, 2 API get thông tin thời tiết còn lại sẽ không cần thực hiện nữa.

Điểm khác nhau giữa race và any là ở trường hợp reject: any sẽ chỉ reject khi tất cả các promise con reject (hay không có promise nào trả về resolve); còn race sẽ reject khi chỉ cần 1 promise con reject. Điều này giúp Promise.race giải quyết bài toán thứ 2 ở trên. Khi có 1 trong 3 task upload file lên server thất bại thì sẽ dừng luôn 2 task còn lại.

Chúng ta có thể tổng kết họ hàng nhà Promise.all như sau:

Promise

Kết bài

Như vậy chúng ta đã đi qua hết được các phương thức xử lý bất đồng bộ Promise.all cùng cách sử dụng chúng trong từng bài toán cụ thể. Trong thực tế dự án sẽ có nhiều trường hợp các bạn sẽ vận dụng kết hợp các function khác nhau, có thể là Promise.all trong Promise.all chẳng hạn. Vì thế hãy nắm vững cách case sử dụng từng function trên nhé. Hy vọng bài viết của mình hữu ích dành cho các bạn.

Tác giả: Phạm Minh Khoa

Xem thêm:

Tìm việc làm IT mới nhất trên TopDev