DEV Community

Taylor Beeston
Taylor Beeston

Posted on • Updated on

Updating callback-style code to use async/await

The problem

If you have been programming in Javascript for a while, you may have run into some code that looks like the following

const example = (url, options) => {
  makeApiRequest((response, error) => { 
    if (error) console.log(error);
    console.log(response);
    // do something with response
  }, url, options);
};
Enter fullscreen mode Exit fullscreen mode

This code uses a callback function to perform an asynchronous task.

The problem with writing asynchronous code like this is maintainability.

  • Code written below the call to makeApiRequest is executed before the code written in the callback function
  • Our additional arguments (url and options) appear after our callback function, which can be difficult to find
  • If we need more asynchronous code within in our callback, we will end up further indented
  • Error handling can become a total nightmare

In the olden days, this is how asynchronous code was written, and as a result of that, there are many libraries and methods that use callback functions like this, so it's fairly likely that you will run into async code using callback functions like this.

However, since the formal adoption of ES6, a better form of asynchronous code has become standardized: Promises. Promises allow us to write asynchronous code that is much more maintainable and easy to follow. If we could magically convert the above code to be Promise-based instead of using callbacks, it might look like this:

const example = (url, options) => {
  makeApiRequest(url, options)
    .then((response) => {
      console.log(response);
      // do something with response
    })
    .catch((error) => console.log(error));
};
Enter fullscreen mode Exit fullscreen mode

This solves most of our maintainability issues with callback functions, and can be made even better with the Async/Await syntactic sugar standardized in ES8.

Using this syntax on our magically Promisified makeApiRequest method would look like this:

const example = async (url, options) => {
  try {
    const response = await makeApiRequest(url, options);
    console.log(response);
    // do something with response
  } catch (error) {
    console.log(error);
  }
};
Enter fullscreen mode Exit fullscreen mode

Using async and await allows us to write code that looks synchronous, but actually performs asynchronous operations. Additionally, it prevents us from accidentally writing code that will happen before our async operation completes, and if we need to add more asynchronous code, we can write all of it in the same try block, making error handling much simpler.

Hooray! If only we could magically 'promisify' our old callback method...

The Solution

I am borrowing the term 'promisify' from the Node.js method that does just that. How does it work? By simply wrapping our old function within a Promise!

const response = await new Promise((resolve, reject) => {
  makeApiRequest((response, error) => {
    if (error) reject(error);
    resolve(response);
  }, url, options);
};
Enter fullscreen mode Exit fullscreen mode

We can further improve this by making our own version of Node's promisify function:

const promisify = (oldFunction) => {
  return ((...args) => (
    new Promise((resolve, reject) => {
      oldFunction((response, error) => {
        if (error) reject(error);
        resolve(response);
      }, ...args);
    });
  ));
}

const makeApiRequestWithPromise = promisify(makeApiReqeust);
Enter fullscreen mode Exit fullscreen mode

Just be aware that our promisify method is dependent on the order of arguments supplied by our makeApiRequest method. Due to that reason, I tend to avoid using a promisify method, and instead just inline the Promise code.

Finally, if you are promisifying a function that does not ever return an error, you can shorten this trick like so:

const example = async () => {
  console.log('3..2..1');
  await new Promise((resolve) => setTimeout(resolve, 1000));
  console.log('Go!');
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)