Skip to content

How to use promises and await with Node.js callback-based functions

Most of the Node.js APIs were built in a time where promises weren’t a thing yet, and they use a callback-based solution.

The typical Node.js API works like this:

doSomething(param, (err, result) => {

})

This also applies to libraries. One example is node-redis, and while working with it on a project, at some point I really had the need to remove all the callbacks, because I had too many levels of callbacks nested into each other - a perfect “callback hell” scenario.

Also, sometimes it’s absolutely necessary to avoid callbacks because you need to return from the function the result of a function call. If that’s returned in a callback, the only way to get the result back would be to send it back with a function, and the callback party continues:

const myFunction = () => {
  doSomething(param, (err, result) => {
    return result //can't return this from `myFunction`
  })
}
const myFunction = callback => {
  doSomething(param, (err, result) => {
    callback(result) //no
  })
}

myFunction(result => {
  console.log(result)
})

There’s an easy solution.

A solution provided by Node.js itself.

We can “promisify” any function that does not support promises (and as a consequence the async/await syntax) by importing promisify from the core Node.js util module:

const { promisify } = require('util')

Then we create new functions using it:

const ahget = promisify(client.hget).bind(client)
const asmembers = promisify(client.smembers).bind(client)
const ahkeys = promisify(client.hkeys).bind(client)

See how I added the a letter to mean async.

Now we can change this example “callback hell”:

client.hget(`user:${req.session.userid}`, 'username', (err, currentUserName) => {
  client.smembers(`followers:${currentUserName}`, (err, followers) => {
    client.hkeys('users', (err, users) => {
      res.render('dashboard', {
        users: users.filter((user) => user !== currentUserName && followers.indexOf(user) === -1)
      })
    })
  })
})

into a much cleaner:

const currentUserName = await ahget(`user:${req.session.userid}`, 'username')
const followers = await asmembers(`followers:${currentUserName}`)    
const users = await ahkeys('users')

res.render('dashboard', {
  users: users.filter((user) => user !== currentUserName && followers.indexOf(user) === -1)
})

This is optimal when using a function you don’t have access to, like in this case where I use a 3rd party library.

Under the hood, promisify wraps the function in a promise, and returns it.

You can do this manually, too, returning a promise from a function, and then using it with async/await:

const handleLogin = (req, user) => {
  return new Promise((resolve, reject) => {
    req.login(user, (err) => {
      if (err) {
        return reject({
          error: true,
          message: err,
        })
      }
      return resolve({
        success: true,
      })
    })
  })
}

//...
const resultLogin = await handleLogin(req, user)

→ Here's my latest YouTube video

→ Get my Node.js Handbook

→ I wrote 17 books to help you become a better developer, download them all at $0 cost by joining my newsletter

JOIN MY CODING BOOTCAMP, an amazing cohort course that will be a huge step up in your coding career - covering React, Next.js - next edition February 2025

Bootcamp 2025

Join the waiting list