Promisification

Promisification – is a long word for a simple transform. It’s conversion of a function that accepts a callback into a function returning a promise.

To be more precise, we create a wrapper-function that does the same, internally calling the original one, but returns a promise.

Such transforms are often needed in real-life, as many functions and libraries are callback-based. But promises are more convenient. So it makes sense to promisify those.

For instance, we have loadScript(src, callback) from the chapter Callback fonksiyonlarına giriş.

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

Let’s promisify it. The new loadScriptPromise(src) function will do the same, but accept only src (no callback) and return a promise.

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(...)

Now loadScriptPromise fits well in our promise-based code.

As we can see, it delegates all the work to the original loadScript, providing its own callback that translates to promise resolve/reject.

As we may need to promisify many functions, it makes sense to use a helper.

That’s actually very simple – promisify(f) below takes a to-promisify function f and returns a wrapper function.

That wrapper does the same as in the code above: returns a promise and passes the call to the original f, tracking the result in a custom callback:

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) {
          return reject(err);
        } else {
          resolve(result);
        }
      }

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

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

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

Here we assume that the original function expects a callback with two arguments (err, result). That’s what we encounter most often. Then our custom callback is in exactly the right format, and promisify works great for such a case.

But what if the original f expects a callback with more arguments callback(err, res1, res2)?

Here’s a modification of promisify that returns an array of multiple callback results:

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

In some cases, err may be absent at all: callback(result), or there’s something exotic in the callback format, then we can promisify such functions without using the helper, manually.

There are also modules with a bit more flexible promisification functions, e.g. es6-promisify. In Node.js, there’s a built-in util.promisify function for that.

Dikkate değer:

Promisification is a great approach, especially when you use async/await (see the next chapter), but not a total replacement for callbacks.

Remember, a promise may have only one result, but a callback may technically be called many times.

So promisification is only meant for functions that call the callback once. Further calls will be ignored.

Eğitim haritası

Yorumlar

yorum yapmadan önce lütfen okuyun...
  • Eğer geliştirme ile alakalı bir öneriniz var ise yorum yerine github konusu gönderiniz.
  • Eğer makalede bir yeri anlamadıysanız lütfen belirtiniz.
  • Koda birkaç satır eklemek için <code> kullanınız, birkaç satır eklemek için ise <pre> kullanın. Eğer 10 satırdan fazla kod ekleyecekseniz plnkr kullanabilirsiniz)