DEV Community

Cover image for Polyfill for Promisify
Supriya M
Supriya M

Posted on

Polyfill for Promisify

In this article, we will learn how to write our implementation of util.promisify(). Before we could get started, there are two things which you must be aware of.

  1. What a callback hell is and how we could come out of it using promise. - Read more
  2. Understanding the implementation of promises in JavaScript. - Read more

Introduction

Promisification helps in dealing with callback-based APIs while keeping code consistent with promises. It converts a function that accepts a callback into a function that returns a promise.

Such transformations are often required in real life, as many functions and libraries are callback-based. Thus, promises are more convenient, so it makes sense to promisify them.

Let's dive into the code and understand this implementation.

//function defintion
const positiveSum = (num1, num2, callback) => {

  if (num1<0 || num2<0) {
    return callback(new Error("Negative numbers are not accepted"), null);
  }
  return callback(null, num1 + num2);
}

//function implementation
positiveSum(2, 3, (err, result) => {
  if (err){
    console.error(err);
  } else {
    console.log(result); 
  }
})

//output:
// 5
Enter fullscreen mode Exit fullscreen mode

We define a function positiveSum that accepts two positive numbers and a callback function. This callback function takes two parameters, error and result.
If either of the two numbers is negative, a new error object is created and passed as the first argument to the callback function. Otherwise, the callback function is returned with the summation of these two numbers that is passed as the second argument within the callback.

We can convert positiveSum to return a promise using util.promisify() as follows:

const util = require('util');

const calculateSum = util.promisify(positiveSum);

calculateSum(2,3).then((res) => {
  console.log(res); // 5
}).catch((err) => {
  console.error(err);
})
Enter fullscreen mode Exit fullscreen mode

Creating our version of promisify

If we observe the above code snippet, promisify accepts a callback as an argument.

Step1 - Creating myPromisify that accepts a callback

const calculateSum = myPromisify(positiveSum);

const myPromisify = (callback) => {
    // implementation logic
}
Enter fullscreen mode Exit fullscreen mode

We also observe that calculateSum is taking two arguments, thus our implementation must return a function that takes two arguments.

Step 2 - Returning a function which could have multiple args

const myPromisify = (callback) => {
    return function(...args){
        // implmentation logic
    }
}
Enter fullscreen mode Exit fullscreen mode

In the above code, you can see that we are spreading arguments because we do not know how many arguments are present within the original function. args will be an array containing all the arguments.

When you call calculate(2, 3) you’re actually calling (...args) => {}. In the implementation above it returns a promise. That’s why you’re able to use calculate(1, 1).then().catch().

Step 3 - Adding a promise to this returning function

const myPromisify = (callback) => {
  return (...args) => {
    return new Promise((resolve, reject) => {

    })
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, we would need to define when to call resolve and reject based on the callback that we pass to positiveSum.

Step 4 - Implementing outcomes of a promise

const myPromisify = (fncallback) => {
  return (...args) => {
    return new Promise((resolve, reject) => {
      function myCallback(err, result) {
       if (err) {
         reject(err)
       }
       else {
         resolve(result);
        }
      }
   })
  }
}
Enter fullscreen mode Exit fullscreen mode

Currently, args[] consists of the arguments passed by calculateSum(2, 3) and not the callback function. So we need to add myCallback(err, result) to the args[] so that positiveSum will call it accordingly as we are tracking the result in myCallback.

Step 5 - Adding myCallback to args[]

const myPromisify = (callback) => {
   return (...args) => {
     return new Promise((resolve, reject) => {
       function myCallback(err, result) {
         if (err) {
           reject(err)
         }
         else {
          resolve(result);
         }
        }
        args.push(myCallback)
        callback.apply(this,args);
      })
  }
}
Enter fullscreen mode Exit fullscreen mode

callback.apply(this, args) will call the original function under the same context with the arguments positiveSum(2,3,myCallback). Then our promisify function should be able to resolve/reject accordingly.

Customize further

The above implementation will work when the original function expects a callback with two arguments, (err, result). That’s what we encounter most often. Then our custom callback is in the correct format and promisify works great for such a case.

But what if the original function expects a callback with more arguments like callback(err, result1, result2, ...)?

In order to make it compatible with that, we need to modify our myPromisify function that will be an advanced version.

const myPromisify = (fn) => {
   return (...args) => {
     return new Promise((resolve, reject) => {
       function customCallback(err, ...results) {
         if (err) {
           return reject(err)
         }
         return resolve(results.length === 1 ? results[0] : results) 
        }
        args.push(customCallback)
        fn.call(this, ...args)
      })
   }
}
Enter fullscreen mode Exit fullscreen mode

Example: A function to calculate the sum and product of 2 numbers

const positiveSumAndProduct = (num1, num2, callback) => {

 if (num1<0 || num2<0) {
    return callback(new Error("Negative numbers are not accepted"), null);
  }

  const sum = num1 + num2;
  const product = num1 * num2;
  return callback(null, sum, product);
}
const mathPromise = myPromisify(positiveSumAndProduct)
mathPromise(2, 3).then(res => console.log(res)) 

//output
// [5, 6]
Enter fullscreen mode Exit fullscreen mode

Hurray! we have successfully implemented our version of util.promisify. Polyfills are one of the questions asked during interviews. I hope this article was helpful

Reach out to me on Twitter if you have any queries. Happy learning! 💻

Peace ✌

Top comments (0)