Async/Await: The Hero JavaScript Deserved

Writing asynchronous code is hard. When it comes to JavaScript we rely heavily on callback functions to accomplish asynchronous tasks which can be far from intuitive. This cognitive overhead creates a barrier to entry for newcomers to programming and the language and even causes frequent heartburn for those of us who have been using the language a while.

In this post we’ll examine how a proposal for ECMAScript 2016 (ES7) can be used to improve our experience with asynchronous programming in JavaScript, making our code easier to understand and simpler to write.

The World of Today

Let’s start by taking a look at an attempt of asynchronous programming today. The following example uses the request library to make an HTTP request to the Ron Swanson Quotes API and prints the response to the console. Give it a spin by pasting the following into a file named app.js and running npm install request to install the dependency. If you don’t have Node.js installed you can grab it from here.

If you’ve worked with asynchronous programming in JavaScript before you might have already spotted why we’re not going to get a quote from this code. If that’s the case then high five.

vnnoqBjIrJ73y.gif

Running it with node app.js you’ll quickly see undefined printed out.

Why does this happen?

The reason that the quote variable is undefined is because the callback function that assigns it is not called until after the call to the request function is finished. But because the request function executes asynchronously, JavaScript does not wait around for it to finish. Instead, it moves on to the next statement which returns the unassigned variable. For a great explanation on how async in JavaScript works under the hood check out this amazing talk by Philip Roberts at JSConf EU.

Synchronous code is generally easier to understand and write because everything executes in the order in which it is written. Return statements are widely used and pretty intuitive in other languages but unfortunately we’re unable to use them as much as we’d like in JavaScript because they don’t work well with JavaScript’s asynchronous nature.

So why go through the struggle of battling asynchronous code? Performance. Network requests and reading from the disk are what we call I/O (input/output) operations. In synchronous I/O execution the program blocks, sitting around and waiting for data transmission to complete. If it takes 60 seconds for a database query to finish the program is sitting around doing nothing for 60 seconds. However during an asynchronous I/O operation the program can resume normal execution and deal with the results of the I/O operation whenever they come up. This is why callback functions exist but callbacks are far more difficult to work with and grok when reading the source of an application.

Utopia

Can we get the best of both worlds – asynchronous code that lets us work with blocking operations but is also easier to read and write? The answer is yes thanks to the ES7 proposal for Asynchronous Functions (Async/Await).

When a function is declared as async it is then able to yield execution to the calling code while it awaits for a promise to be resolved. If you’re not familiar with promises check out one of these great resources.

You can replace the code in app.js with the following. We’ll also need to install the Babel transpiler to run it. Do so with npm install babel. Babel will transpile our bleeding edge ES7 code into a version that is runnable in today’s environment. You can learn more about Babel here.

You can see that we are returning a promise that wraps the network request inside our new getQuote function.

Inside the callback passed to request we are calling the resolve function of the promise with the body of the result.

Execute the following to run this example.

Woah. That code looks pretty cool and is close to the original attempt. It looks pretty synchronous even though it’s not.

In case you didn’t notice, Ron once said, was printed out first despite being called after main. This shows that we’re not blocking while waiting for the network request to complete.

Making Improvements

We can actually improve this further by adding error handling with a try/catch block. If there is an error during the request we can then call the reject function of the promise which will be caught as an error inside of main. Like return statements, try/catch blocks were very underused in the past because they were hard to use correctly with asynchronous code.

Run this code again and you can see the exception get caught by changing the request URL to something like http://foo.

Benefits

These are some pretty awesome benefits that are going to really change the way we write asynchronous JavaScript. Being able to write code that runs asynchronously, but looks synchronous and makes it easier to use common programming constructs like return and try/catch will certainly help make the language more approachable.

The best part is that we can use our new favorite feature with anything that returns a promise. Let’s take the Twilio Node.js library for example. If you haven’t used the Twilio Node.js library before you can read more about it here. You’ll also need to have a Twilio account which you can sign up for here.

Start by running npm install twilio. Then paste the following into a file named twilio.js and replace the fields in the lines marked // TODO with your own credentials and numbers.

Just like we showed above with the getQuote function, we’ve marked sendTextMessage as async which allows it to await the resolution of the promise returned from client.sendMessage.

Wrapping it Up

We’ve seen how we can take advantage of a proposed ES7 feature to improve our experience writing asynchronous JavaScript.

I’m really excited for the Async/Await proposal to move forward but while we wait for that we’re able to use Babel to take advantage of this today with anything that returns a promise. The proposal recently hit the candidate stage (stage 3) and is in need of usage and feedback. Have a go at using it with the next awesome thing that you build and be sure to let me know on in the comments or on Twitter @eddiezane.

  • Craig Lafferty

    At last!

  • Please notice that unless this has changed in the last few days, ES7 features such as async/await or Rest Object Spread are not enabled by default in Babel, and you need to configure it explicitly. Nice post.

    • No, you’re wrong. ObjectRestSpread and async/await are included by default in babel as stage 2.
      See here: https://babeljs.io/docs/usage/experimental/

      • But AFAIK stage 0 is the default isn’t it? No, you’re right, seems like a few things have indeed changed, nevermind.

        • Stage 2 and above is default. I also recommend don’t change it without good reason, stage 2 covers all basic needs.

          • Jeff Hansen

            Then you obviously haven’t used ES7 decorators – I regret trying them out because now I can’t live without them.

          • I think it’s bad idea to use not-standardized features because babel can remove it someday. Of course if it’s your pet project you can do what you want :)

          • Jeff Hansen

            Angular2 and Aurelia, both relatively popular frameworks rely heavily on decorators – I think I’m safe. :)

  • Slava Ganzin

    Generators, async/await, promises and all that stuff to avoid writing good async code. I bless God that fanboys didn’t reinvent AOP or event programming for that.

    Waiting for mutex and semaphore in es2020.

    • Bill Gates

      fuck, you so right…

    • Yaohan Chen

      Those are useful in concurrent programming, but JavaScript isn’t. Async JavaScript is still single-threaded.

      • GifCo

        single-threaded and Async arnt the same thing.

  • Hi, I try to babel the async/await using the presets stage2, But when it run, the browser log the error: Uncaught ReferenceError: regeneratorRuntime is not defined. And Can you give me some advice or a demo? Thank you : )

    • J

      Make sure to install the babel-polyfill and then add a requre(‘babel-polyfill’) to the top of your file.

  • Skywalker13
  • upq

    @craiglafferty:disqus AGREE!

  • Stuart Robinson

    deprecated babel@6.5.2: Babel’s CLI commands have been moved from the babel package to the babel-cli package

  • AlGantori

    instead of request assume your are checking for a prop to become true (ie. instead of your request) something like this:

    if (this.GlobalService.Ready == true)
    How would you keep retrying until it succeeds, still handling the rare case where it would fail. Please assist me with an actual code snippet. I am jumping directly into typescript and can map your JS into.
    Thank you.

    • I’m not entirely sure what you’re asking here. Are you using async/await in your code? Is your code asynchronous at all? It looks like you’re just checking a value, is something setting that value by an asynchronous process?

      I’d like to help, but do you have a more fully formed snippet that I could read, understand and help with?