Promisification Trong JavaScript

Tổng quan về Promisification

"Promisification” là một thuật ngữ để chỉ sự chuyển đổi. Ở đây nó chỉ sự chuyển đổi từ function callback sang một function trả lại một promise . Cách convert như vậy thường được sử dụng nhiều trong thực tế, vì nhiều function và thư viện được xây dựng dựa trên callback. Tuy nhiên việc sử dụng promise sẽ mang lại nhiều thuận tiện hơn.

Để hiểu rõ hơn, chúng ta hãy xem ví dụ, chúng ta có loadScript(src, callback). Từ các bài học trước, bạn có thể xem lại ở đây

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) => {...})

Function này tải một script với giá trị đã cho src, rồi gọi callback(err) trong trường hợp có lỗi hoặc callback(null, script) trong trường hợp thành công. 

Bây giờ chúng ta sẽ tạo một hàm mới loadScriptPromise(src), chức năng này cũng hoạt động tương tự như function ở trên, nhưng nó trả về một promises.

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ờ loadScriptPromise phù hợp tốt với code trên promise. Nếu chúng ta thích promise hơn callback thì chúng ta hoàn toàn có thể sử dụng nó để thay thế. Trong thực tế, chúng ta có thể cần promisify hơn là một function, vì vậy sẽ hợp lý khi sử dụng nó. Chúng ta sẽ gọi nó là promisify(f): nó chấp nhận một promisify function f và trả về một wrapper function

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, function callback trong function promisify ban đầu có hai đối số (err, result). Nhưng điều gì sẽ xảy ra nếu nó có một callback với nhiều đối số hơn callback(err, res1, res2, ...) ? Vì vậy phiên bản nâng cao hơn hơn của nó ra đời promisify.

  • Khi function là promisify(f), nó sẽ hoạt động tương tự như phiên bản cũ ở trên.
  • Khi function là promisify(f, true), nó sẽ trả về promise bằng mảng kết quả callback. Đó là chính xác là những gì chúng ta cần cho các callback với nhiều đối số.
// 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 => ...);

Như bạn có thể thấy, về cơ bản nó giống như trên, nhưng resolve được gọi với chỉ một hoặc tất cả các đối số tùy thuộc vào việc có đúng hay không manyArgs. Để biết thêm các định dạng callback, chúng ta có thể sử dụng promisify mà không cần sử dụng các helper. Ngoài ra còn có các mô-đun có chức năng promisification, ví dụ như es6-promisify . Trong Node.js, có util.promisify tích hợp cho việc đó.

Tổng kết

Vậy là bài học ngày hôm nay của chúng ta đã khép lại. Để giúp các bạn thuật tiện hơn trong quá trình thực hành và tránh các sai sót chúng ta sẽ điểm lại một số lưu ý quan trọng ở đây. Hẹn gặp lại các bạn ở bài sau.

  • 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, nhưng không phải là sự thay thế hoàn toàn cho callback.
  • Hãy nhớ rằng, một promise có thể chỉ có một kết quả, nhưng về mặt kỹ thuật, một callback có thể gồm nhiều các callback khác bên trong.
  • Vì vậy, Promisification chỉ dành cho các hàm callback không lồng nhau. Các callback tiếp theo sẽ bị bỏ qua
Trần Quang Hào
SUNTECH VIỆT NAM   Đăng ký để nhận thông báo mới nhất