Write Your Own Promisify Function from Scratch

This guide requires prior knowledge of callback and promises

Introduction

In this article, we will learn how to write your own promisify function from scratch

Promisification helps in dealing with callback-based APIs while keeping code consistent with promises.



Its good to just wrap any function with new Promise() and not worry at all but we can’t do it when we have many functions and it will be redundant as well.



If you understand promises then learning how to write promisify function will be smooth

But have you ever wondered how promisify works?

The important thing is not to stop questioning. Curiosity has its own reason for existence. — Albert Einstein

Promises have been introduced in the ECMA-262 Standard, 6th Edition (ES6) that has been published in June 2015.

It was a quite good upgrade over callbacks as we all know the benefits of callback hells how much code was readable 🙂

As a node js developer, you should know what is a promise and how does it work internally which will also help you in JS interviews.

Why do we need to convert callback to promise?

In Callbacks, if you want to do something sequentially you will have to specify err argument in each callback which is redundant while in promises or async-await you can just add .catch method or block which will catch the errors occurred in the promise chain In Callbacks, You have no control over how many times it’s being called which can lead to memory leaks, when its called and under what context. Using promises, we control these factors especially error handling so the code is more readable and maintainable.

How to Make Callback-based function return Promise?

There are two ways to do it.

Wrap function into another function which returns a promise and resolves or reject based on callback arguments Promisification — We create util/helper function promisify which will transform all error first callback-based APIs.

Example: There’s a callback-based API which provides the sum of two numbers and we want to promisify it so it returns thenable promise.

const getSumAsync = (num1, num2, callback) => { if (!num1 || !num2) { return callback(new Error("Missing arguments"), null); } return callback(null, num1 + num2); } getSumAsync(1, 1, (err, result) => { if (err){ doSomethingWithError(err) }else { console.log(result) // 2 } })

Change and Wrap into a promise

const getSumPromise = (num1, num2) => { return new Promise((resolve, reject) => { getSumAsync(num1, num2, (err, result) => { if (err){ reject(err) }else { resolve(result) } }) }) }

As you can see getSumPromise delegates all the work to original function getSumAsync providing its own callback that translates to promise resolve/reject .

Promisify

When we need to promisify many functions we can create a helper function promisify

What is Promisification?

Promisification means transformation. It’s a conversion of a function that accepts a callback into a function returning a promise.

Using nodejs util.promisify()

const { promisify } = require('util') const getSumPromise = promisify(getSumAsync) // step 1 getSumPromise(1, 1) // step 2 .then(result => { console.log(result) }) .catch(err =>{ doSomethingWithError(err); })

So it looks like magic function which is transforming getSumAsync into getSumPromise which have .then and .catch methods

Let’s write our own promisify function:

if you see step 1 in above code promisify function accepts a function as an argument so the first thing we have to do write a function that can do the same.

const getSumPromise = myPromisify(getSumAsync) const myPromisify = (fn) => {}

After that, getSumPromise(1, 1) is a function call which means that our promisify should return another function which can be called with the same arguments of the original function

const myPromisify = (fn) => { return (...args) => { } }

In the above code if you see we are spreading arguments because we don’t know how many arguments original function has. args will be an array containing all the arguments.

When you call getSumPromise(1, 1) you’re actually calling (...args)=> {} and in the implementation above it returns a promise that’s why you’re able to use getSumPromise(1, 1).then(..).catch(..)

I hope you’ve got the hint that wrapper function (...args) => {} should return a promise.

Return a promise

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

Now the tricky part is how to decide when to resolve or reject a promise.

Actually that will be decided by original getSumAsync function implementation as it will call the original callback function and we just need to define it and based on err and result we will reject and resolve promise.

const myPromisify = (fn) => { return (...args) => { return new Promise((resolve, reject) => { function customCallback(err, result) { if (err) { reject(err) }else { resolve(result); } } }) } }

Our args[] only consist of arguments passed by getSumPromise(1, 1) except the callback function, so you need to add customCallback(err, result) to the args[] which original function getSumAsync will call accordingly as we are tracking result in customCallback .

Push customCallback to args[]

const myPromisify = (fn) => { return (...args) => { return new Promise((resolve, reject) => { function customCallback(err, result) { if (err) { reject(err) }else { resolve(result); } } args.push(customCallback) fn.call(this, ...args) }) } }

As you can see we have added fn.call(this, args) which will call the original function under the same context with arguments getSumAsync(1, 1, customCallback) and then our promisify function should be able to resolve/reject accordingly.

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 exactly the right format and promisify works great for such a case.

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

In order to make compatible with that, we need to modify our myPromisify function which 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) }) } }

Example:

const getSumAsync = (num1, num2, callback) => { if (!num1 || !num2) { return callback(new Error("Missing dependencies"), null); } const sum = num1 + num2; const message = `Sum is ${sum}` return callback(null, sum, message); } const getSumPromise = myPromisify(getSumAsync) getSumPromise(2, 3).then(arrayOfResults) // [6, 'Sum is 6']

That’s all guys! Thank you for making it this far!

I hope you’re able to grasp the concept. Try to re-read it again it’s a bit of code to wrap around the mind but not complex and let me know if it was helpful 🙂

Don’t forget to share it with your friends who are starting with nodejs or need to level up nodejs skills.

References:

https://nodejs.org/dist/latest-v8.x/docs/api/util.html#util_util_promisify_original

https://github.com/digitaldesignlabs/es6-promisify