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.

functiongetMovieNames(){letdisplayToUser='';letmovieNames=["Lord of the Rings","Band of Brothers","Interstellar"];movieNames.forEach(movieName=>displayToUser+=movieName+'');returndisplayToUser;}alert(getMovieNames());

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?

functiongetReleaseDates(){letdisplayToUser='';letmovieNames=["Lord of the Rings","Band of Brothers","Interstellar"];movieNames.forEach(movieName=>displayToUser+=fetchReleaseDate(movieName));returndisplayToUser;}alert(getReleaseDates());

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.

asyncfunctiongetReleaseDates(){letdisplayToUser='';letmovieNames=["Lord of the Rings","Band of Brothers","Interstellar"];movieNames.forEach(asyncmovieName=>displayToUser+=awaitfetchReleaseDate(movieName));returndisplayToUser;}alert(awaitgetReleaseDates());

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.

asyncfunctiongetReleaseDates(){letdisplayToUser='';letmovieNames=["Lord of the Rings","Band of Brothers","Interstellar"];for(movieNameofmovieNames){displayToUser+=awaitfetchReleaseDate(movieName);}returndisplayToUser;}alert(awaitgetReleaseDates());

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.