Convert Javascript Callbacks to Promises

Convert Javascript Callbacks to Promises

A few months back I had to integrate Redis with a server. You can imagine my surprise when I discovered that Redis has no method which uses promises. Well, most of the code used the Async...Await and this one piece of code would have to use callbacks, which was not ideal. I felt like sharing the solutions because there were some solutions that I didn't know about. But first let us answer the question about why are callbacks undesirable.

Why are callbacks undesirable?

Improper use of callbacks leads to a problem called 'Callback Hell'. This happens when we have so much code nested in functions that the code cannot be read easily. Unclean code leads to more errors because it is harder to understand. We can see the problem in the snippet below, (source: callbackhell.com)

fs.readdir(source, function (err, files) {
  if (err) {
    console.log('Error finding files: ' + err)
  } else {
    files.forEach(function (filename, fileIndex) {
      console.log(filename)
      gm(source + filename).size(function (err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err)
        } else {
          console.log(filename + ' : ' + values)
          aspect = (values.width / values.height)
          widths.forEach(function (width, widthIndex) {
            height = Math.round(width / aspect)
            console.log('resizing ' + filename + 'to ' + height + 'x' + height)
            this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
              if (err) console.log('Error writing file: ' + err)
            })
          }.bind(this))
        }
      })
    })
  }
})

With the introduction of Async...Await in the latest versions of Javascript, the asynchronous syntax has become much cleaner. If you are using this flow, you should consider promisification of your traditional callback functions.

Solution - Promisify the callback

There are many solutions to this problem. You could use any one of the following solutions covered below:

  1. Node.js promisify method
  2. Bluebird promisify (in case your code already uses Bluebird)
  3. You could even write your own function to promisify (although it is better to use some library for simplicity).

You can read more about how Bluebird promisify works here . I will show you how to promisify the functions, so for my Redis get function...

DIY - Function that converts callbacks to promises

To solve this, a new Promise has to be created and returned using the callback. This function can then be used everywhere in the code. This would be something like:

const redis = require("redis");
const client = redis.createClient();
const getAsync = function promisifyAsync(key) {
    return new Promise((resolve, reject) => {
        client.get(key, (err, values) => {
            if (err) reject(err)
            else resolve(values);
        });
    })
}

Node.js in-built promisification

If the problem is on the server side, Node.js has a built-in promisification library, this makes it easy to just use it out of the box.

const redis = require("redis");
const client = redis.createClient();
const { promisify } = require("util");
const getAsync = promisify(client.get).bind(client);

Bluebird Promisification

Bluebird's promisify function works even on the client side. This library searches for methods in the object you pass inside promisifyAll, then appends Async to the end of those functions and finally adds it back to the object that was passed. So the promise equivalent of the get function of redis would be getAsync. I have to admit this is pretty cool.

const bluebird = require("bluebird");
const redis = require("redis");
const client = redis.createClient();
bluebird.promisifyAll(client)

And for implementing the functions created the async...await syntax can be used, which makes the code much much cleaner.

async function getValuesFromRedis(key) {
    try {
        const result = await getAsync(key)
        console.log(result)
        return result
    } catch (error) {
        console.error(error)
    }
}

PS: Promisification is great for your async...await flow, but for some use cases this method does not work. A promise may have only one result, but a callback can be called many times. So promisification is only meant for functions that call the callback once. Further calls will be ignored.

If you think I've missed any other way let me know down in the comments, I would love to hear your thoughts.