Async JavaScript, there and back again

Async programming in JavaScript has always been a fairly advanced, convoluted, and somewhat annoying concept. I think every JS programmer gets asked more than one async question in an interview. Here’s one of my favorites:

var Api = function() {
var ret = [];
    for (var i = 0; i < 3; i++) {
ret.push(function() {
return i * 2;
});
}
    return ret;
};
var data = Api();
console.log(data[0]())
console.log(data[1]())
console.log(data[2]())

I’m sure you’ve seen this one before, but the question is “what does this program log”? How do we fix the bug? Why does the bug happen? For bonus points, does ES6 give us any tools that helps us with this scenario, and so on.

The point of this post isn’t to answer this question, but you’re welcome to spend on time on it, maybe post an awesome answer in the comments. The point of this post is to talk about Async JavaScript — where we used to be, where we are now, and where we’re going. (Hint, we’re making progress!)

Ugh, Remember this?

function getData(userId) {

var cb = function(data) {
if (!data) {
// apart from getUser and getOrders needing their own
// errorHandling, all our other callbacks need
// them as well :(
throw new Error('There was an error retrieving data!');
};
document.querySelector('#user')
.innerHTML = JSON.stringify(data);
};
    // aync request user
getUser(
userId,
// async callback to be fired
// when getUser finishes
// binding another callback to be fired
// when getOrders finishes
getOrders.bind(this, cb)
);
};

In this scenario, we need to make an API call to get a user, and upon the return of that call, we need to request some orders that the user made. Upon the return of that call, we can finally append that info to the DOM. This is just gross code, but it used to be all we had.

And then we got Promises

Promises are an inversion of our normal workflow, but they make for much more tenable code. Take a look at this:

const getData = userId => getUser(userId)
.catch(handleError)
.then(getOrders)
.catch(handleError)
.then(data => document.querySelector('#user')
.innerHTML = JSON.stringify(data));

Ok sure, I’m using ES6, so it looks prettier, but it’s also simpler. And simpler is always better. I think the most important thing here, is that we’re able to provide a default error handling utility to each API call, without having to gross up the code inside those methods. It’s also easier to quickly scan through — it’s very obvious that getOrders is chained to getUser, and it’s much more obvious what is happening with the final data.

Oh man, now we get Await

I think of all the ES6 and ES7 features, await is going to have the most effect on producing quality code.

async function getData(userId) {
try {
const user = await getUser(userId);
const order = await getOrders(user);
        document.querySelector('#user')
.innerHTML = JSON.stringify(data);
}
    catch (e) {
throw new Error('There was an error retrieving data!');
}
};

Let’s look at what is awesome about this:

  1. We’re able to wrap both of our API calls in a single try/catch and we can handle our error dynamically, based on the e argument passed to our catch clause.
  2. We don’t need to jump through hoops to chain/structure our code any differently than we would if this code was entirely synchronous.
  3. It’s super obvious what is happening.

Conclusion

None of this is ground breaking news — the await syntax has been complete for a while now, and it’s stage-4 which means it’s nearly set in stone. But it’s nice to see where we used to be, how promises revolutionized the work flow, and how await is going to make dealing with async processes even simpler.