Polyfill for Promisify
Published: 2022-06-04 12:45:35

In this article, we will learn how to write our implementation of [util.promisify()](https://nodejs.org/dist/latest-v8.x/docs/api/util.html#util_util_promisify_original). 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](http://callbackhell.com/)
2. Understanding the implementation of promises in JavaScript. - [Read more](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) 

## 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.

```javascript
//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
```
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:

```javascript
const util = require('util');

const calculateSum = util.promisify(positiveSum);

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

## 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
```javascript
const calculateSum = myPromisify(positiveSum);

const myPromisify = (callback) => {
    // implementation logic
}
```

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_
```javascript
const myPromisify = (callback) => {
    return function(...args){
        // implmentation logic
    }
}
```
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
```javascript
const myPromisify = (callback) => {
  return (...args) => {
    return new Promise((resolve, reject) => {
      
    })
  }
}
```
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
```javascript
const myPromisify = (fncallback) => {
  return (...args) => {
    return new Promise((resolve, reject) => {
      function myCallback(err, result) {
       if (err) {
         reject(err)
       }
       else {
         resolve(result);
        }
      }
   })
  }
}
```
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[]`
```javascript
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);
      })
  }
}
```
`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.

```javascript
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)
      })
   }
}
```

Example: A function to calculate the sum and product of 2 numbers
```javascript
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]
```

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](https://twitter.com/supminn) if you have any queries. Happy learning! 💻

Peace ✌