Async/Await: The Hero JavaScript Deserved

October 02, 2015
Written by

asyncawait

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.

var request = require('request');

function getQuote() {
  var quote;

  request('http://ron-swanson-quotes.herokuapp.com/v2/quotes', function(error, response, body) {
    quote = body;
  });

  return quote;
}

function main() {
  var quote = getQuote();
  console.log(quote);
}

main();

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.


var request = require('request');

function getQuote() {
  var quote;

  return new Promise(function(resolve, reject) {
    request('http://ron-swanson-quotes.herokuapp.com/v2/quotes', function(error, response, body) {
      quote = body;

      resolve(quote);
    });
  });
}
 
async function main() {
  var quote = await getQuote();
  console.log(quote);
}

main();
console.log('Ron once said,');

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.

./node_modules/.bin/babel-node app.js

// Ron once said,
// {"quote":"Breakfast food can serve many purposes."}

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.

var request = require('request');

function getQuote() {
  return new Promise(function(resolve, reject) {
    request('http://ron-swanson-quotes.herokuapp.com/v2/quotes', function(error, response, body) {
      if (error) return reject(error);
      resolve(body);
    });
  });
}

async function main() {
  try {
    var quote = await getQuote();
    console.log(quote);
  } catch(error) {
    console.error(error);
  }
}

main();
console.log('Ron once said,');

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.

./node_modules/.bin/babel-node twilio.js

var twilio = require('twilio');

var client = new twilio('YOUR_TWILIO_ACCOUNT_SID', 'YOUR_TWILIO_AUTH_TOKEN'); // TODO

async function sendTextMessage(to) {
  try {
    await client.messages.create({
      to: to,
      from: 'YOUR_TWILIO_PHONE_NUMBER', // TODO
      body: 'Hello, Async/Await!'
    });
    console.log('Request sent');
  } catch(error) {
    console.error(error);
  }
}

sendTextMessage('YOUR_PHONE_NUMBER'); // TODO
console.log('I print out first to show I am async!');

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. 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. And be sure to try Twilio’s JavaScript and Node.js Tutorials and Guides over on our documentation site.