Using Callbacks With Async/Await
Tags: javascript, beginners, webdev, programming
Published: 2022-10-24 10:24:20

*This article was originally published at https://maximorlov.com/using-callbacks-with-async-await/*

Let's face it. Sometimes you have to use an asynchronous function that uses the old callback-based approach. It might be part of a legacy codebase, or it could come from a library that hasn't had an update in over 5 years. 🤷‍♂️

Nevertheless, there's no way around it and you have to deal with it somehow.

So what do you do? Do you add messy callbacks to your code? 🙅‍♂️

What if I told you there is a way to **make callback-based asynchronous functions work nicely with async/await?** Not only that, but you won't have to make *any* changes to existing code.

I'll teach you how to change a function's signature, even when you don't have control over its implementation.

## Transforming callback-based asynchronous functions

Suppose we have a `getUserAddress` function that's written using modern async/await. Inside this function, we're calling `fetchUser` which uses a callback-based approach.

When `fetchUser` completes, it calls the callback with the user object. Our goal is to have `getUserAddress` return the user's address.

```js
async function getUserAddress(userId) {
  fetchUser(userId, (err, user) => {
    if (err) {
      console.log(err);
      return;
    }

    user.address; // we want this value
  });
}

const address = await getUserAddress(8); // to be returned here
console.log(address); // undefined
```

How do we accomplish that?

We can't simply return `user.address` inside the callback function because the return value would go to the parent function that calls the callback, which is the `fetchUser` function and *not* `getUserAddress`.

```js
async function getUserAddress(userId) {
  fetchUser(userId, (err, user) => {
    if (err) {
      console.log(err);
      return;
    }

    // 🚫 doesn't work, value is returned to fetchUser function
    return user.address;
  });
}

const address = await getUserAddress(8);
console.log(address); // undefined
```

And returning `fetchUser` doesn't work either because by design the function doesn't return anything. In fact, **all callback-based asynchronous functions return undefined**.

```js
async function getUserAddress(userId) {
  // 🚫 doesn't work, fetchUser doesn't return anything
  return fetchUser(userId, (err, user) => {
    if (err) {
      console.log(err);
      return;
    }

    user.address;
  });
}

const address = await getUserAddress(8);
console.log(address); // undefined
```

Well then, the only way to use `fetchUser` with async/await is if the function returns a Promise. **Callback-based functions are incompatible with promises & async/await** because they are different approaches to writing asynchronous code.

But how do we make `fetchUser` return a Promise when we don't have control over its implementation?

We wrap it! 🌯

If we wrap `fetchUser` inside a promise that resolves with the user value and rejects with the error object, we can await the promise and return the user's address inside `getUserAddress`.

```js
async function getUserAddress(userId) {
  // Create and await a promise that rejects and resolves
  // with the callback's `err` and `user` arguments, respectively
  try {
    const user = await new Promise((resolve, reject) => {
      fetchUser(userId, (err, user) => {
        if (err) {
          reject(err);
          return;
        }

        resolve(user);
      });
    });
    return user.address;
  } catch (error) {
    console.log(error);
  }
}

const address = await getUserAddress(8);
console.log(address); // '729 Ellsworth Summit, Aliyaview'
```

What this does is it creates a bridge between the asynchronous callback and async/await. It translates the success and error paths inside the callback to the resolve and rejection states of the promise.

When the callback is called with an `err` argument the promise will reject with the error object which is then caught and logged in the catch block. If there is no error, the promise resolves with the user which is assigned to a variable.

Finally, we call `getUserAddress` which returns a promise, [because all async functions return a promise](https://maximorlov.com/understanding-async-await/), and await it to get the user's address.

## Using a utility function
It quickly becomes tedious if you have to manually wrap all callback-based functions in your project. Luckily, Node.js comes with a utility function that does this for you.

The `promisify` method from the built-in `util` module takes a callback-based asynchronous function and returns a promise-based version. You call the promise-based function with the same parameters as you would call the callback-based function, except instead of passing in a callback you await the promise for the resolved value.

```js
import util from "node:util";
import { fetchUser } from "../utils/api.js";

// util.promisify transforms a callback-based function
// into a promise-based equivalent
const fetchUserAsync = util.promisify(fetchUser);

async function getUserAddress(userId) {
  try {
    const user = await fetchUserAsync(userId);
    return user.address;
  } catch (error) {
    console.log(error);
  }
}

const address = await getUserAddress(8);
console.log(address); // '729 Ellsworth Summit, Aliyaview'
```

This is much better than what we've started with, and it works! 🎉

You've learned how to transform a callback-based asynchronous function into a function that returns a promise despite not having control over its internal implementation. There's no need to write asynchronous JavaScript code using a callback-based approach anymore.

## Master Asynchronous JavaScript 🚀

Learn how to write modern and easy-to-read asynchronous code with a **FREE 5-day email course**.

Through visual graphics you will learn how to decompose async code into individual parts and put them back together using a modern async/await approach. Moreover, with **30+ real-world exercises** you'll transform knowledge into a practical skill that will make you a better developer.

![Refactoring Callbacks, a FREE 5-day email course. 30+ real-world exercises, a visual guide and 5 days, 5 lessons.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ic1d6os14bjflg46fuyw.png)

👉 [**Get Lesson 1 now**](https://maximorlov.com/refactoring-callbacks-course/)