Promisification trực tiếp là một từ dài cho một chuyển đổi đơn giản. Đó là việc chuyển đổi một hàm chấp nhận một cuộc gọi lại thành một hàm trả lại một lời hứa.

Các phép biến đổi như vậy thường được yêu cầu trong cuộc sống thực, vì nhiều hàm và thư viện dựa trên gọi lại. Nhưng lời hứa sẽ thuận tiện hơn, vì vậy nó có ý nghĩa để hứa hẹn chúng.

Ví dụ, chúng tôi có loadScript(src, callback)từ chương Giới thiệu: cuộc gọi lại(Callbacks).

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

function loadScript(src, callback) {
  let script = document.createElement('script');
  script.src = src;

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

  document.head.append(script);
}

// usage:
// loadScript('path/script.js', (err, script) => {...})

Hãy quảng bá nó. Hàm mới loadScriptPromise(src)đạt được kết quả tương tự, nhưng nó chỉ chấp nhận src(không callback) và trả lại một lời hứa.

let loadScriptPromise = function(src) {
  return new Promise((resolve, reject) => {
    loadScript(src, (err, script) => {
      if (err) reject(err)
      else resolve(script);
    });
  })
}

// usage:
// loadScriptPromise('path/script.js').then(...)

Bây giờ loadScriptPromiserất phù hợp trong code dựa trên lời hứa.

Như chúng ta có thể thấy, nó ủy thác tất cả các công việc cho bản gốc loadScript, cung cấp cuộc gọi lại riêng của nó có nghĩa là hứa hẹn resolve/reject.

Trong thực tế có lẽ chúng ta sẽ cần phải quảng bá nhiều chức năng, vì vậy sẽ rất hợp lý khi sử dụng một trình trợ giúp. Chúng tôi sẽ gọi nó promisify(f): nó chấp nhận một chức năng để quảng cáo fvà trả về một hàm bao bọc.

Trình bao bọc đó thực hiện giống như trong đoạn code trên: trả về một lời hứa và chuyển cuộc gọi đến bản gốc f, theo dõi kết quả trong một cuộc gọi lại tùy chỉnh:

function promisify(f) {
  return function (...args) { // return a wrapper-function
    return new Promise((resolve, reject) => {
      function callback(err, result) { // our custom callback for f
        if (err) {
          reject(err);
        } else {
          resolve(result);
        }
      }

      args.push(callback); // append our custom callback to the end of f arguments

      f.call(this, ...args); // call the original function
    });
  };
};

// usage:
let loadScriptPromise = promisify(loadScript);
loadScriptPromise(...).then(...);

Ở đây chúng ta giả sử rằng hàm ban đầu mong đợi một cuộc gọi lại với hai đối số (err, result). Đó là những gì chúng ta gặp phải thường xuyên nhất. Sau đó, cuộc gọi lại tùy chỉnh của chúng ta có định dạng chính xác và promisifyhoạt động tuyệt vời cho trường hợp như vậy.

Nhưng điều gì sẽ xảy ra nếu bản gốc fmong đợi một cuộc gọi lại với nhiều đối số hơn callback(err, res1, res2, ...)?

Đây là phiên bản nâng cao hơn của promisify: nếu được gọi là promisify(f, true), kết quả hứa hẹn sẽ là một loạt các kết quả gọi lại [res1, res2, ...]:

// promisify(f, true) to get array of results
function promisify(f, manyArgs = false) {
  return function (...args) {
    return new Promise((resolve, reject) => {
      function callback(err, ...results) { // our custom callback for f
        if (err) {
          reject(err);
        } else {
          // resolve with all callback results if manyArgs is specified
          resolve(manyArgs ? results : results[0]);
        }
      }

      args.push(callback);

      f.call(this, ...args);
    });
  };
};

// usage:
f = promisify(f, true);
f(...).then(arrayOfResults => ..., err => ...)

Đối với các định dạng gọi lại kỳ lạ hơn, giống như các định dạng không có err: callback(result)chúng ta có thể quảng bá các hàm đó một cách thủ công mà không cần sử dụng trình trợ giúp.

Ngoài ra còn có các mô-đun với các hàm quảng cáo linh hoạt hơn một chút, ví dụ es6-promisify. Trong Node.js, có một hàm dựng sẵn util.promisifycho điều đó.

Xin lưu ý:

Promisification là một cách tiếp cận tuyệt vời, đặc biệt là khi bạn sử dụng async/await(xem chương tiếp theo), nhưng không phải là sự thay thế hoàn toàn cho các cuộc gọi lại.

Hãy nhớ rằng, một lời hứa có thể chỉ có một kết quả, nhưng về mặt kỹ thuật có thể được gọi nhiều lần.

Vì vậy, việc quảng bá chỉ có nghĩa đối với các hàm gọi lại cuộc gọi một lần. Các cuộc gọi tiếp theo sẽ bị bỏ qua.

Full series tự học Javascript từ cơ bản tới nâng cao tại đây nha.

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!