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

Join the AI Workshop to learn more about AI and how it can be applied to web development. Next cohort February 1st, 2026

The AI-first Web Development BOOTCAMP cohort starts February 24th, 2026. 10 weeks of intensive training and hands-on projects.


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)

Lessons in this unit:

0: Introduction
1: Installing Node.js on your computer
2: How to write your first Node.js program
3: Importing other files
4: Using npm to install packages
5: Using built-in modules
6: How to use the Node.js REPL
7: Reading files with Node
8: Writing files with Node
9: Build an HTTP Server
10: The Node Event emitter
11: How to read environment variables from Node.js
12: Node Buffers
13: Node.js Streams
14: ▶︎ How to use promises and await with Node.js callback-based functions
15: Modern package managers (pnpm and Bun)