Skip to content

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

New Course Coming Soon:

Get Really Good at Git

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)
Are you intimidated by Git? Can’t figure out merge vs rebase? Are you afraid of screwing up something any time you have to do something in Git? Do you rely on ChatGPT or random people’s answer on StackOverflow to fix your problems? Your coworkers are tired of explaining Git to you all the time? Git is something we all need to use, but few of us really master it. I created this course to improve your Git (and GitHub) knowledge at a radical level. A course that helps you feel less frustrated with Git. Launching Summer 2024. Join the waiting list!
→ Get my Node.js Handbook
→ Read my Node.js Tutorial on The Valley of Code

Here is how can I help you: