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)
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