In the world of Node.js, many APIs and libraries were built before the introduction of promises. As a result, they rely on a callback-based solution. However, working with nested callbacks can lead to a complex and messy code structure commonly known as “callback hell.” Thankfully, there is a solution: the use of promises and the await keyword.

To remove callbacks and make use of promises, Node.js provides a useful utility called promisify from the util module. By utilizing promisify, you can convert callback-based functions into functions that support promises and enable the use of the async/await syntax.

To use promisify, import it from the util module:

const { promisify } = require('util');

Then, create new functions using promisify:

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

By adding the letter “a” as a prefix to the function name, it indicates that the function is now asynchronous.

With these promisified functions, you can transform a callback-based “callback hell” scenario into a cleaner and more readable code structure by using the async/await syntax:

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 approach is particularly useful when working with third-party libraries where you don’t have direct access to modify the original callback-based functions.

Under the hood, the promisify function wraps the original function in a promise, allowing you to use await to handle the asynchronous behavior.

Alternatively, you can manually create a promise and return it from a function, allowing you to use async/await syntax:

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

By using promises and await, you can simplify the handling of callback-based functions and create more readable and maintainable code.