Promises Chaining Trong JavaScript

Chào các bạn! Ở các bài học trước chúng ta đã biết được trong lập trình bất đồng bộ, việc có quá nhiều các callback lồng nhau (nested callback) thì sẽ dễ dẫn đến tình trạng callback hell. Và để giải quyết tình trạng đó chúng ta có thể sử dụng promises (đã được giới thiệu trong bài Làm việc với promises trong JavaScript). Vậy bạn đã từng thắc mắc nếu một chuỗi các promise được sử dụng liên tiếp thì được gọi là gì không? Và nó sẽ được sử dụng như thế nào? Tất cả những thắc mắc đó sẽ được giải đáp trong bài ngày hôm nay. Chào mừng các bạn đến với bài học Promises Chaining

Promise chaining là gì?

Giả sử promise A kiểm tra xem có người dùng tồn tại hay không, promise B thêm người dùng mới, promise C thực thi điều gì đó bằng cách sử dụng các thông tin người dùng cấp. Nếu người dùng không tồn tại, chúng ta gọi promise B. Nếu người dùng đã tồn tại, chúng tôi gọi trực tiếp promise C. Đây chính là promise chaining.

Cú pháp và cách sử dụng promise chaining

Về cơ bản promise chaining sẽ trông như thế này:

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000); // (*)

}).then(function(result) { // (**)

  alert(result); // 1
  return result * 2;

}).then(function(result) { // (***)

  alert(result); // 2
  return result * 2;

}).then(function(result) {

  alert(result); // 4
  return result * 2;

});

Ý tưởng ở đây là kết quả được chuyển qua chuỗi các trình xử lý .then.

  • Promise ban đầu sẽ được giải quyết sau 1 giây (*),
  • Sau đó, các .then được gọi (**).
  • Giá trị mà nó trả về được chuyển cho chuỗi các trình xử lí .then tiếp theo(***)
  • …và cứ như thế.

Khi kết quả được truyền dọc theo chuỗi các trình xử lý, chúng ta có thể thấy một chuỗi các alert lệnh gọi: 1→ 2→ 4.

Toàn bộ đều hoạt động, bởi vì  promise.then sẽ trả về một promise, vì vậy chúng ta có thể chuyển tiếp đến .then tiếp theo.

Một lỗi hay gặp phải ở người mới là chúng ta có thể thêm nhiều .then vào một promise duy nhất. Tuy nhiên đây không phải là chuỗi (Chaining). Ví dụ

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

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

Những gì chúng ta thấy ở đây chỉ là một số xử lý cho một promise. Chúng không chuyển kết quả cho nhau, thay vào đó chúng được xử lý một cách độc lập.

Đây là hình ảnh (so sánh nó với chuỗi ở trên):

Tất cả .then trên cùng một promise nhận được cùng một kết quả - kết quả của promise đó. Vì vậy, trong đoạn code trên tất cả các alert hiển thị giống nhau: resolve(1).

Returning promises

Một handler (trình xử lí) được sử dụng trong .then(handler) có thể tạo và trả về một promise. Trong trường này, các handler tiếp theo sẽ đợi và sau đó nó sẽ nhận được kết quả. Ví dụ

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000);

}).then(function(result) {

  alert(result); // 1

  return new Promise((resolve, reject) => { // (*)
    setTimeout(() => resolve(result * 2), 1000);
  });

}).then(function(result) { // (**)

  alert(result); // 2

  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 2), 1000);
  });

}).then(function(result) {

  alert(result); // 4

});

Đầu tiên .then hiển thị 1 và trả về new Promise(…) trong dòng (*). Sau một giây, nó sẽ giải quyết và kết quả ( là đối số của resolve, đây chính là  result * 2) được chuyển cho trình xử lý thứ 2 của  .then. Trình xử lý đó nằm trong dòng (**), nó hiển thị 2. Vì vậy, đầu ra giống như trong ví dụ trước: 1 → 2 → 4, nhưng bây giờ nó sẽ có độ trễ 1 giây giữa alert.

Returning promises cho phép chúng ta xây dựng chuỗi các hành động bất đồng bộ.

Xây dựng function loadScript

Tiếp tục kế thừa từ 2 bài học trước. Bây giờ chúng ta tiếp tục sử dụng funtion loadScript để tải 1 tập tin.

loadScript("/article/promise-chaining/one.js")
  .then(function(script) {
    return loadScript("/article/promise-chaining/two.js");
  })
  .then(function(script) {
    return loadScript("/article/promise-chaining/three.js");
  })
  .then(function(script) {
    // use functions declared in scripts
    // to show that they indeed loaded
    one();
    two();
    three();
  });

Chúng ta có thể bỏ return và viết gọn lại như sau:

loadScript("/article/promise-chaining/one.js")
  .then(script => loadScript("/article/promise-chaining/two.js"))
  .then(script => loadScript("/article/promise-chaining/three.js"))
  .then(script => {
    // scripts are loaded, we can use functions declared there
    one();
    two();
    three();
  });

Ở đây mỗi loadScript trả về một promise và lệnh tiếp theo  .then được chạy và giải quyết xong thì nó bắt đầu tải tập lệnh tiếp theo. Vì vậy, các tập lệnh được tải lần lượt và chúng ta có thể thêm nhiều hành động bất đồng bộ hơn vào chuỗi

Về mặt kỹ thuật, chúng ta có thể thêm .then trực tiếp vào từng function loadScript, như thế này:

loadScript("/article/promise-chaining/one.js").then(script1 => {
  loadScript("/article/promise-chaining/two.js").then(script2 => {
    loadScript("/article/promise-chaining/three.js").then(script3 => {
      // this function has access to variables script1, script2 and script3
      one();
      two();
      three();
    });
  });
});

Tổng kết

Với một .then(hoặc catch/finally, ...) trình xử lý sẽ trả về một promise, phần còn lại của chuỗi sẽ đợi cho đến khi nó được giải quyết xong. Khi nó xử lí xong, kết quả (hoặc lỗi) của nó sẽ được truyền. Vậy là bài học thứ 3 trong series tìm hiểu về promises, async/await đã kết thúc. Hẹn gặp lại các bạn ở bài viết tiếp theo error handling với promises




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