Asynchronous patterns in Javascript

Pierre Guilhou
Tech & Product at Spendesk
5 min readJan 26, 2018

--

Pierre Guilhou — Software engineer at Spendesk.

One of the best features of Javascript is its asynchronously-driven core. Everything is asynchronous, which gives the ability to craft complex event-driven applications. However, this powerful engine can be quite overwhelming to grasp sometimes, and a small recap of the commonly used patterns can’t hurt anyone.

That’s the main goal of this post! We’ll start by going back in time to the old callback way, then catching up with the promises, and finally diving a bit deeper with some really nice ES2017 features.

Using callbacks

The easy way

Historically, the first and most used Javascript pattern was callbacks. Its goal was to give a function, during runtime, as an argument to another function. This gives Javascript — and NodeJS — the ability to run a whole bunch of lines of code without being blocked by waiting for some time-consuming actions (e.g HTTP calls, Database queries etc.), only firing the callback when the underlying action is ready. A few lines of code are worth a thousand words:

Simple callbacks

In this example, we are retrieving a user in the database in a non-blocking way, which means that the variable bank will be logged before the user name. That’s the power of Javascript, which basically makes it really fast.

Scaling up

The downside of using such an approach is that it can quickly become tricky when dealing with nested callbacks. A basic example could be the following one:

The naughty callback hell

This is often referred as the callback hell, as it’s really hard to read. Besides, when your project scales, it becomes really hard to maintain by your team and might lead to critical bugs.

Using promises

What’s a promise?

The natural cure for the callback hell is to start using promises, which allows you to make complex asynchronous operations without having to use callbacks. By definition, promises are the representation of an eventual completion — or deferred result — of some asynchronous operation. What makes it pretty awesome is that the result of a promise might be another promise, hence making it composable. What does this mean in practice? That you can chain your promises (using the then keyword). Another fundamentally nice thing about this is that the code almost looks like a logical sentence.

So, how do we create a promise? A basic and naive implementation could be the following one, where all you have to do is to instanciate a new Promise object, that takes a function as an argument. This function takes two parameters that you can use to either resolve a result once everything is done, or to nicely fail with an error if something unexpected happened.

A simple way to promisify a callback-based function

This is a really simple example, but it points out that you should always catch the potential errors raised along the way–Always keep your promises! In addition, the Promise object also has two static methods used to immediately resolve or reject a single result. This is sometimes useful if you don’t need to wait for an asynchronous outcome to resolve something:

You can also use Promise.resolve and Promise.reject to immediately fulfill your promises

Promises in series

Take a look at the example below, which does exactly what the first callback example did, assuming that all the database functions now return promises:

Simple chain of promises

See? The same logic is much easier to read and reason about. It almost describes what we want to do in a natural language.

The only problem here is that if we wanted to use the user or company variables within the final then statement, we would have to store them in global variables and assign them when we actually find them, in order to be able to use them later. Another approach would be to nest out promises, having to deal with each level of nesting’s errors, like so:

Nested promises in action

Better than callbacks but emh… still nasty, right? We’ll see how to handle this kind of async flow in a few minutes but first, let’s dive into parallel operations flows using promises.

Promises in parallel

Sometimes, we do not need to wait for the outcome of a promise’s to do another unrelated action, even if we need both results to move forward. For instance, imagine a case where we need to create a financial transaction for a given bank account, but we have to check the stock price of the base currency before being able to do so. A way to do this could be to use parallel promises, just like so:

Promises used in parallel

This way, both promises will be launched simultaneously, and we’ll wait for both of their outcomes to carry out the following tasks.

Now we have an overview of how to handle both simple and complex asynchronous flow in series and parallel using promises. Nevertheless, it can still feel tricky to read and understand with a simple glance.

Using async / await

The 2017 Ecmascript spec offered a new way of dealing with asynchronous functions, based on promises. This new pattern — async/await — makes async code look like sync, procedural code. Under the hood, this feature uses simple promises wrapped in a neater way. In addition, without having to depend on Babel, Node 8+ now supports it natively!

Warming up

So, how does it work in practice? Quite simple actually, you just have to declare your asynchronous function using the async keyword, and prefix its internal asynchronous lines with the await keyword. Take a look at the following example, that roughly does what we tried to do before using promises, this time using the async/await syntax:

Basic async/await example

Pretty neat, right? This combines the power of Javascript’s asynchronous promises to the comfort of the old school, easy-to-read procedural code. The only thing to keep in mind is that async/await functions should wrap their await calls with try/catch blocks because when something bad happens along the way, they throw an exception. If you don’t catch them right away, make sure you have a way to catch them somewhere else in your code.

Parallel flows using async/await

As we did before using regular promises, we can also write parallel flows using the async/await paradigm:

Parallel flows using the async / await syntaxt

This is particularly important to use parallel flows as much as you can if there’s no immediate dependency between your asynchronous lines of codes. Indeed, if you don’t do so, your code will basically have to wait for each line to move to the next one (i.e creating nested chains of promises under the hood). It can quickly become an anti-pattern.

You can basically do everything using this new syntax, when you understand that what an async function returns is a promise.

Combining async flows with array utils

Finally, here’s a bonus showing you how to use this paradigm in a more tricky example. Let’s say that you want to count the total heritage owned by legal-age people of a given cohort:

Mixing async/await and common array functions

Wrapping up

We’ve covered a few ways of dealing with asynchronous operations in Javascript. Either by using the good ol’ callbacks, by using native promises or the async / await pattern. These different approaches all have their strengths and weaknesses, and should always be covered up with catch fallbacks to make the experience really smooth for the end user–and for your own sake as well!

This is the first episode of a series around Javascript patterns and best practices, some other episodes might come soon! Feel free to drop a comment about the patterns you’re using on a daily basis, or by reaching out to us at dev@spendesk.com. We’re always looking for talented engineers who like to solve deep challenges at Spendesk. In the meantime, stay tuned!

--

--