JavaScript Promisification

Promisification is a long term for a straightforward transformation. It represents a conversion of the function, which accepts a callback into a function returning a promise.

Often, transformations like that are needed in real-life because multiple libraries and functions are based on callbacks. But, promises are much more convenient.

Let’s consider a load_Script(src, callback):

function load_Script(src, callback) {
  let scriptJS = document.createElement('script');
  scriptJS.src = src;
  scriptJS.onload = () => callback(null, scriptJS);
  scriptJS.onerror = () => callback(new Error(`Script load error for ${src}`));
  document.head.append(scriptJS);
}
// usage:
// load_Script('path/script.js', (err, scriptJS) => {...})

Now, we are going to promisify it. The new load_Script_Promise(src) will get the same result accepting merely src. It returns a promise, like this:

let load_Script_Promise = function (src) {
  return new Promise((resolve, reject) => {
    load_Script(src, (err, script) => {
      if (err) reject(err)
      else resolve(script);
    });
  })
}
// usage:
// load_Script_Promise('path/script.js').then(...)

So, it can be stated that ow load_Script_Promise suits well in a promise-based code. It delegates the overall work to the original load_Script and provides its callback, which translates to promise resolve/reject.

Now, it’s necessary to use a helper. It can be called promisify(fn). It includes a to-promisify function f, returning a wrapper function.

That wrapper does the same as the code, described above. It returns a promise and passes the call to the original fn.

The result is tracked in a custom callback, like here:

function promisify(fn) {
  return function (...args) { // return  wrapper function
    return new Promise((resolve, reject) => {
      function callback(err, result) { // our custom callback for fn
        if (err) {
          reject(err);
        } else {
          resolve(result);
        }
      }
      args.push(callback); // append custom callback at the end of the fn arguments 
      fn.call(this, ...args); // call the original function
    });
  };
};
// usage:
let load_Script_Promise = promisify(load_Script);
load_Script_Promise(...).then(...);

In the example above, the function waits for a callback along with two arguments such as result and err. So, when the callback is in the correct format, the promisify operates more efficiently.

Now, let’s discover a more complicated version of the promisify. After calling promisify(fn, true), the result of the promise will be an array of callback results, like this:

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

Also, there exist modules that have more flexible functions for promisification.

A built-in function, called util.promisify is used in Node.js.

Summary

Promisification is a useful and widespread approach that makes a developer’s life more productive. It is especially great while using it with async/await. However, the complete replacement for callbacks is not recommended.

It is important to note that a promise can have a single result. On the contrary, a callback can be called multiple times.

So, promisification is used for the functions, calling the callback once. Upcoming calls are always ignored.

Practice Your Knowledge

What is the main function of promisification in JavaScript?

Quiz Time: Test Your Skills!

Ready to challenge what you've learned? Dive into our interactive quizzes for a deeper understanding and a fun way to reinforce your knowledge.

Do you find this helpful?