Async Javascript Evolution

Async/Await: Promises reached adulthood and gentle backstory of async I/O in javascript and the return to mental sanity

Simone Corsi
7 min readJan 16, 2018

Some of us seems to have forgotten those dark days, when our codebases were a mess of nested monstrosities spanning several level deep in spiral of endless unforgiving pain. We got fancies new constructs and we got away from that at the speed of light to never mention it again to forget the shame we had to endure for writing those pyramid of doom. Yeah, I’m talking about that. Callback hells. I still see them around, never ending monolith of an already ended age.

Actually not Javascript nor callbacks, but I liked the image!

Why I’m writing this? because I still see a wide adoption of callback pattern instead of switching at least to Promises and toasync/await — mostly by frontend developers which are used to work with plain javascript or jQuery based workflow — and new comers to Node.js from PHP .

The point is, it’s not about a matter of preference or being used to callbacks, or your personal coding style, it’s about code readability and maintainability. Good code document itself — and we can talk on this for hours without agreeing, but still it’s something one should at least try to achieve — writing a never ending pyramid of doom it’s not a good idea neither for your or who is unlucky enough to inherit your codebase.

It’s not about a matter of preference or being used to callbacks, it’s about code readability and maintainability.

Pyramid of doom and believe me I saw things that this…

The Pyramid of Doom

Way back to ’00s the only way to handle I/O requests in Ajax was over XMLHttpRequest constructor, all the libraries were written to resolve or rejection through callbacks passed as parameters to the main wrapper of an XHR request that were execute at a given time during the cycle of life of the request. This was the only way to handle it at the time, as you may see, or remember, that workflow was all but intuitive to use, read or debug. A lot of libraries were based on this and also the popular Node.js non blocking I/O handling was based on this so it began to root it’s way into the workflow of everyone making codebase a mess of infinite indentation impossible to read even with 2 monitor, the example in the photo it’s not even the worst thing I’ve saw in the years, and I’m you know what I’m talking about.

Promises to the rescue

Promise were introduced as a successor to XMLHttpRequest to handle I/O in a better and somehow standard way which from all allowed us to turn this:

callToService(function(response) {
// do something with response and then
return maybeCallAnotherService(function(response) {
return doSomethingWithIt(response)
}, function(error) {
return handleError(error)
})
}, function(error) {
return handleError(error)
})

into this:

callToService()
.then(response => {
// do something with it then
return maybeCallAnotherService()
})
.then(response => {
// do something with it
return doSomethingWithIt(response)
})
.catch(error => {
return handleError(error)
})

As you may see this made thing a little bit more readable, here we are using a really basic scenario not taking into account real cases like request retry/timeout/fail-recovery/whatsoever. With this kind of approach we got nice things like bluebird adding some nice methods to allow us to parallelize calls or wait for a bunch of calls to finish before doing something and more. But still, as the codebase growth and complexity arose we got the same problem as before, infinite chaining and nested chais of thens/catch that made your head explodes.

Promises are still great and you should definitely learn about them if you still work in callbacks and because you’ll do a step ahead and improve your works but also because the next step is closely related to them, so take your time if still find yourself lacking the subject to learn it, here you can find a good series about Promises.

Generators

A little backstory without going into much details, one of the cool things introduced with ES6 was Generators. This new construct allowed functions to be “paused” at every yield statements returning a generator object containing the value returned by the expression evaluated by the yield and a next callback to resume the function until the next yield. This construct is also tied with Iterators if you want to dig deeper.

Your head is exploding? Right, well as simple as I may put it, this new construct allowed us to start writing async code as if it was synchronous pausing execution while not blocking parsers and event loops and resuming/getting results when done.
But it was kind of useless without a lot of extra setup and some thunk/runner functions wrapper over Generators, that meant an increased complexity in the syntax, but there was a lot of potential in this and a lot of tools were created to have a better syntax and use this new construct to handle also Promise in an kind of synchronous-alike way, tools like Co were created and also an entire framework for Node.js — which I was kind of in love with at the time — Koa.js was based on Generators — v2 is based on async/await give it a look :)

Async/Await, the new cool kid in the block

Luckily for us the need of the community to have a better syntax over generators was listened and we got fancy async/await which in fact is basically a syntactic sugar over Generators and Promises, the previous example can now be written as simple as :

async function someIOCalls() {
let response = await callToservice()
// do something with response
let response = await maybeCallAnotherService()
// do some logics
return manipulatedReponse
}

Isn’t it gorgeous?
What you have to understand is that an async function declaration return and AsyncFunction object which is similar to a Generator one in the way that its execution can be halted but the difference is that it will always return a Promise regardless if you return an IO/async statement or a primitive value if the expression return so, this allow us to reuse them even further with other async functions!

To handle errors all you have to do is wrap your async function around a try/catch block:

try {
let response = await someIOCalls()
} catch(error) {
//handle error or retry
}

And that’s it. As said before being in nature strictly related to Promises you must understand how they work to fully grasp and use async/await.

You can combine async functions with already existing Promise libraries to handle more cases like promise serialization and parallelization, await won’t care what you are trying to achieve as long as you provide it a Promise to execute like Promise.all([..]) or bluebird’s Promise.some and so on so forth.

async function() {
let response = await Promise.all([call(), callTwo()]
return manipulatedResponse
)}

You can even use them as IIFE:

(async ()=> {
let response = await something()
// Do something
})()

Async function can also be used, as you see above, with arrow function, and you must understand that async/await keyword are strictly related and must be used in pairs, that mean that the function containing await should be an async one, remember this when you find yourself in a case were you have a callback that you want to turn into an async function.

doSomething(async () => {
// the inner callback arrow function
// can be used as an async function
})

To be able to use it in your current workflow you must be on Node.js on a version > 8 for (or 7 behind experimental flag). If you are working on the browser you must use Babel to turn it into es2015 because older browser won’t be able to support it lacking fundamental Generators support.

I’ll do a little followup on how to integrate a light babel workflow into your static frontend’s projects without too much difficulty in the next days for those who don’t need too much configurations but still want to start using ES6+ feature into their projects so keep it tuned for that.

Question: Is async/await not blocking?

Yes and No. As a closing note I want to highlight this because is still something I get asked or saw people getting wrong about async/await, the fact that async functions return promises does not mean that your code doesn’t block the execution of your script if you put CPU intensive work inside them.

The fact that async functions return promises does not mean that your code doesn’t block the execution of your script if you put CPU intensive work inside them.

Async function gets “paused” on await which schedule the resume of the rest of the function when the promise on his right side expression resolves, but if you are doing extensive calculations in between awaits you should know that you are blocking your event loop and you should consider moving all your heavy works on an external web worker (if in the browser) as a better alternative if you want to have better performance.

Conclusion

That was meant to be a gentle introduction because I see a lot of frontend guy still tied to their workflow reluctant to switch mostly because of all the new tools and stuff they have to learn which may seems overwhelming at first, it will payback in the long run.

Don’t be the one building pyramids in 2018, learn to go async and don’t await any longer, be the hero your teammates deserve and start writing readable codebase for your own and others mental sanity!

--

--

Simone Corsi
Simone Corsi

Written by Simone Corsi

Hi I'm yet anotherBackend Engineer! I build things, play with things and I’m curious about everything, probably even too much!

Responses (1)