DEV Community

Cover image for Using Async / Await with Callbacks
megazear7
megazear7

Posted on

Using Async / Await with Callbacks

I recently ran into the problem of getting a promise interface working alongside a callback interface. Specifically I had an array of promises and wanted to iterate over the values with JavaScript's forEach method. As we will see this is problematic, however a solution is readily available. It took me a while to understand what was going on, but hopefully you can skip the pain that I had as I will explain it all here.

Disclaimer: The code in this blog post was reduced to highlight the source of the problem. This is not the best way to implement this functionality in JavaScript. It is instead meant to make the problem and resolution clear.

A promise-less starting point

I started with an array of values. I performed some logic on this array combining the values into one and then returned the result.

function getMovieNames() {
  let displayToUser = '';
  let movieNames = [
    "Lord of the Rings",
    "Band of Brothers",
    "Interstellar"
  ];

  movieNames.forEach(movieName =>
    displayToUser += movieName + ' ');

  return displayToUser;
}

alert(getMovieNames());
Enter fullscreen mode Exit fullscreen mode

This is simple enough: It alerts a combined string of movie names. No problems so far.

A promise filled update

For reasons external to the problem at hand, I had to take the data that I was receiving and fetch other data with it. Now instead of having an array of values we iterated over these values and retrieved asynchronous data. Can you guess what get's returned?

function getReleaseDates() {
  let displayToUser = '';
  let movieNames = [
    "Lord of the Rings",
    "Band of Brothers",
    "Interstellar"
  ];

  movieNames.forEach(movieName =>
    displayToUser += fetchReleaseDate(movieName));

  return displayToUser;
}

alert(getReleaseDates());
Enter fullscreen mode Exit fullscreen mode

An empty string! Of course you can't know without seeing the implementation of fetchReleaseDate, but if we assume it is making an HTTP request or taking some similarly asynchronous action, then getReleaseDates is returned before the forEach callback is ever executed.

Await to the rescue

If we need to await for the fetchReleaseDate method, it seems like the await keyword should work for that. Right? In order to await for fetchReleaseDate the function it is in needs to be labeled as async. In this case that is the callback that we are giving the forEach method.

async function getReleaseDates() {
  let displayToUser = '';
  let movieNames = [
    "Lord of the Rings",
    "Band of Brothers",
    "Interstellar"
  ];

  movieNames.forEach(async movieName =>
    displayToUser += await fetchReleaseDate(movieName));

  return displayToUser;
}

alert(await getReleaseDates());
Enter fullscreen mode Exit fullscreen mode

Not so fast! Even if we await for the fetchReleaseDate method, the forEach method does not await for our callback. And so the result is no different than before.

For of to the rescue

If the callback not being awaited on is the problem, the solution is to remove the callback. To do this let's change out the use of forEach with a for...of block as shown below.

async function getReleaseDates() {
  let displayToUser = '';
  let movieNames = [
    "Lord of the Rings",
    "Band of Brothers",
    "Interstellar"
  ];

  for (movieName of movieNames) {
    displayToUser += await fetchReleaseDate(movieName);
  }

  return displayToUser;
}

alert(await getReleaseDates());
Enter fullscreen mode Exit fullscreen mode

Finally! It works again. While this may be obvious to some, others like me who are addicted to the forEach and map style of using arrays may not see this gotchya with using async / await along side callbacks.

Parting tip: If you ever declare a callback function as async in order to use the await keyword inside, ask yourself first: "Is the function I am calling going to await on my callback?" If the answer is no, then you will need to refactor your use of the callback function.

Check out my blog for more of my musings upon technology and various other topics.

Oldest comments (0)