JavaScript for Enterprise Development — Part 5: Handling Async Operations

Russell Briggs
5 min readMar 11, 2017

--

One of the major advantages of NodeJS over other web platforms is its high performance and low resource usage, due to its asynchronous I/O model.

This article covers how asynchronous operations are handled in the standard JavaScript ES6 language (and TypeScript) using Promises.

UPDATE: I’ve written an updated version of this article, discussing the new Async Functions that were added in ES2017 (ES8)

JavaScript is Synchronous

At its core, JavaScript is a synchronous, single-threaded language. This means that the JavaScript interpreter is only ever processing one part of your program at a time.

This makes it easy to reason about your program, because a sequence of JavaScript code will always run to completion, without any state changes caused by other parts of the program.

This is in contrast to multi-threaded languages such as C or Java, who’s functions can be paused at any time, and their data modified by other parts of the application.

But surely single-threading is bad for performance?!

Its true - if you write a JavaScript web application to calculate Pi to 10 billion digits, your web server will not be able to serve many requests! However most consumer-facing applications do not require this level of CPU-intensive calculation.

Computers are really fast these days, and the most programs spend most their time waiting for external things like disk access, network data or user input.

JavaScript (and therefore NodeJS) use an Event Loop, and trigger long-running external processes to run asynchronously (in the background). This allows the interpreter to be available to process other parts of the program, while waiting for the result of asynchronous operations.

Working with Asynchronous Operations — Callbacks

Callback functions are the most basic way to handle asynchronous operations in JS. Lets have a look at an example that gets data from an API:

JavaScript Single Callback Example — its ok…

In this example we are calling the request function with the url we want to request data from, and passing it a callback function, for it to run later, once the data is received.

The output from this program is:

Got Here.
Got user data: { ...data }

“Got Here.” is printed before the results of the request because the callback function is called asynchronously once the data is received from the server.

Callback Hell

OK now lets have a look at a second example, which combines two APIs.

Things are starting to get ugly…

Nested callbacks — ugly as, bro!

The output from this program is:

Got Here.
Ahmet says: An aspirational diet will have you dreaming of success..

To combine the results of two asynchronous operations, we’ve had to nest one callback inside another. You can see that as we add more complexity to our program, using callbacks quickly become unmanageable. This is known as Callback Hell. You don’t want to go there!! ;)

The main problems with using callbacks are:

  • Multiple layers of nesting make the code hard to follow
  • It is easy to accidentally overwrite variables because nested functions share their parent’s variable scope
  • There is no unified error handling — if an error occurs in the chain you just don’t get a result — this can be very frustrating to debug!
  • What if I want to trigger both API calls simultaneously?

Handling Asynchronous Operations Nicely — Promises

Fortunately ES6 introduced Promises as a core language feature, which make asynchronous code way easier to manage. A Promise is an object that wraps-up an asynchronous operation, and either eventually succeeds (“resolves”) or fails (“rejects”).

Instead of nesting callback functions inside each other, you create a “Promise Chain”, which is simply a list of functions that will be run one after the other.

Each function in the chain can either return a value (such as a string or object), or another promise. If you return a promise, then the next function in the chain will not execute until that action has completed.

Lets have a look at the above example rewritten to use promises:

Using promises to handle async actions — better!

The output of this program is:

Ahmet says: An aspirational diet will have you dreaming of success..
Got Here: Ahmet

In the example above, we are calling a request() function, which returns a Promise. We are then calling the .then() function on that promise, to create the action that will occur once that promise is successfully resolved (i.e. once the data is received from the API).

In the first .then() action, we are making another call to request() to get more data. We return the Promise from this request, so that the following .then() function will only execute once the second request has completed.

The .catch() handler will execute if an error occurs in any of the preceeding .then() functions.

Advantages of this approach:

  • The logic runs in the order that you write it — much easier to understand and maintain.
  • Functions are not nested, so there is no risk of accidentally overwriting the state of a parent function. You can share state between actions using variables in the parent scope, as we did with user and quote above.
  • Built-in support for error handling. Any run-time errors that occur in any of the .then() handlers are passed right down to the .catch() handler — no further .then() calls are processed.

Taking advantage of JavaScript’s Awesome Asynchronous Operation support

JavaScript ES6 Promises offer some great additional functions that allow you to take full advantage of asynchronous I/O:

  • Promise.all() — Triggers multiple asynchronous actions at once, and waits for them all to complete before calling the .then() function
  • Promise.race() — Triggers multiple asynchronous actions at once, and calls the .then() function as soon as the first action completes.

Lets change our code to make both API calls at the same time:

Using Promise.all() — sweet as!

The above code runs faster because both API calls are triggered simultaneously instead of one after the other. The .then() function is called with an array containing the results of both promises. The .catch() function will be called with the error if either of the requests fail.

Summary

So we’ve seen that using traditional callbacks can quickly lead to Callback Hell, so should be avoided for Enterprise applications where possible!

Promises offer a much neater and more powerful solution to handling asynchrony. It is also possible (and quite straightforward) to wrap up callback-based libraries in Promises. There are some good examples of how to do this, as well as lots of other technical info on promises here.

There is a TypeScript project containing the above examples here if you’d like to have a play with them.

Cool! Whats next?!

I feel I’ve now touched on all the fundamentals that developers new to JavaScript would need to be aware of, so its time to start covering some practical topics like building web applications!

In the next article I’ll cover building a basic web application using HapiJS

--

--

Russell Briggs

CTO at Junari Ltd, the leading provider of customised management software for business — junari.com