JavaScript Shinies: Async/Await

We’re starting off fairly gently with a JavaScript feature that’s pretty much landed. You shouldn’t need to even use something like Babel for this to work.

The Problem We Solve With It

Let’s get some data from an API in something like a React component lifecycle hook. I don’t want to tie this to React because it doesn’t matter, so I’m going to mangle the lifecycle hook’s name.

componentWasCreated(props) {
const post = http.get(`http://api/posts/?slug=${props.slug}`);
return http.get(`http://myapi/posts/${post.id}/comments`);
}

This may (and should) give people some twitches. This is definitively asynchronous code. What’s going to happen here isn’t super obvious and is a trap for young players. What I expect to happen as a naive assumption is that it will get the post as an ajax request, and then use the post object returned to get an id, which it then uses to get comments for that post. (Note that I don’t return the post object because I don’t care about it, I just want the comments. No idea why, this is bullshit code.)

I’ve written about promises before and I don’t want to labour it, so the solution here is that the http library above returns promises, and the promise is “chained” with the then function. To rewrite the above code we want to nest this functionality accordingly.

componentWasCreated(props) {
return http.get(`http://api/posts/?slug=${props.slug}`).then(
function(post){
return http.get(`http://myapi/posts/${post.id}/comments`);
}
);
}

This will now do as expected. I think. To be completely honest I’m not 100% sure. Promises when used this way aren’t particularly intuitive. Am I returning the promise? Or the result of the promise? Yes I am! Aside from that, the continual nesting inside a then call is also a fairly unintuitive pattern, and can lead to some deep and fairly impenetrable code, that often works the reverse of what you would expect.

Enter Await

The point of Async and await, a pair of new keywords, is to straighten this out and basically make the previous naive implementation work as expected. You add the await keyword to the async call to tell your code not to continue until it’s done with this call first. You use the async keyword in order to warn the “compiler” that an await is coming and not to poop on the bed.

async componentWasCreated(props) {
const post = await http.get(`http://api/posts/?slug=${props.slug}`);
return await http.get(`http://myapi/posts/${post.id}/comments`);
}

Note that by using this await keyword all our promises kind of do what we expect them to. The async elements no longer return promises that we then have to do something with, they return the result of the call we made.

Await is a blunt instrument. Once you discover it, it can be tempting to use it all the time, because it flattens out any async issues. But bear in mind that using await basically makes your code synchronous. While there are plenty of cases where you want to flatten out the asynchrony there are also cases where you don’t. If your code makes sense to keep asynchronous, and do things in parallel, you will lose that benefit by using await.

For the most part, though, it’s truly invaluable. I do a lot of unit testing of blockchain functionality, which involves typically using JavaScript to call the blockchain as an RPC request, and then doing a bunch of assertions on the values. The use of async and await means these tests can look relatively simple and comprehensible. Instead of this.

it("should add a new product", function() {  var itemName = "TestItem";
var itemPrice = 1000;
HashMarket.deployed().then(function(instance) {
instance.addNewItem(itemName, itemPrice).then(function(item) {
instance.getItem(item.id).then(function(result) {
var [price, seller] = result;
assert.equal(itemPrice, price, "Price not properly added");
assert.equal(itemSeller, seller, "Seller not added");
});
});
});
});

By using Async/Await (as well as other ES6+ features) we can simplify this code out a lot. Not only do we stop the chaining and nesting, but we also remove some temporary variables, and clean the overall structure.

it("should add a new product", function() {  const itemName = "TestItem";
const itemPrice = 1000;
const instance = await HashMarket.deployed();
const itemId = await instance.addNewItem(itemName, itemPrice);
const [price, seller] = await instance.getItem(itemId);
assert.equal(itemPrice, price, "Price wasn't properly added");
assert.equal(itemSeller, seller, "Seller not added");
});

Definitely an improvement.

Enabling This Feature

There should be no effort required. If you’re using recent versions of NodeJS it will work out of the box. And if you’re targeting recent browsers it should work. At worst case the basic Babel setup most frameworks work with will transpile this out.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store