Handling Async Operations in JavaScript in 2018

Russell Briggs
3 min readMay 26, 2018

--

JavaScript at its core is a single-threaded, event-driven programming language. In order to remain performant, all “blocking” operations such as network requests and hardware access are performed asynchronously.

What this means is that, instead of your code simply waiting for the operation to complete, you have to register an event listener callback function that gets called when the operation completes.

Promises, Promises

As described in my first article about handling async operations, Promises (introduced in ES2015/ES6) are a great way to handle async callbacks, because they give us a standardised way of chaining actions and handling errors, as shown in the example below:

In the example above we create a function that outputs a User and a Random Quote sequentially. The great thing about this structure is:

  • We don’t have any nested callbacks
  • The operations happen one after the other
  • If an error occurs in one step, all the following steps are automatically skipped and the catch() handler is invoked
  • We return the Promise object, which means a caller of the getUserQuote() function can call .then() and .catch() on it, to chain other actions, e.g:
getUserQuote()
.then(() => console.log('Profit!!'))
.catch((err) => console.log('No profit for you! :('));

So yeah, Promises are awesome, but theres still a few issues:

  • Defining lots of .then() and .catch() functions can still get a bit messy and confusing
  • Doing Loops with async calls is not very easy — you have to recursively call your function from a .then()method.
    (Trying to use a for { } loop will just trigger all the async calls at once, which is fine if thats what you want!)

Enter JavaScript Async Functions!

Async Functions, introduced in ES2017/ES8, make async in JavaScript AWESOME!

Lets take a look at our example above, re-written using an async function:

Yep, that really is all the code needed! :) So whats going on here?

  1. We define the getUserQuote()function as an async function. What this means is that the function will always return a promise, which will either resolve successfully, or reject with an error.
  2. Within our async function, we await any async operations — an async operation is any other function that returns a Promise.
  3. If an awaited operation throws an error, or its promise is rejected, our getUserQuote() function will automatically reject. You can wrap the await statement in a normal try / catch block to catch these errors.
  4. Once an awaited operation succeeds, the returned value from the promise will be stored in the assigned variable, and the async function will continue to the next statement.

You’ll notice we’re not returning a value from our getUserQuote()function — there is no need to manually return a Promise, however you can use a normal return statement to either:

  • Return a single value, which will be added as “resolved” value of the async function’s promise.
  • Return a promise, such as the request() function’s result, i.e.
return request({ url: 'https://randomuser.me/api/'});

Consumers of our getUserQuote() function can then either await it themselves, or call .then() and/or .catch() to handle the result.

Async Proceedures

Now we can use await inside functions, it would be cool to use it everywhere!

If you want to use await at the top-level of your JavaScript program, a neat trick is to use an async “Immediately Executed Function Expression” (IIFE). Sounds complicated, but it isn’t really …

Basically the code

(async () => {  /* async code here */  })();

Defines an async function, and executes it immediately. The advantage of this is that now you don’t need to call .then() or .catch() anywhere in your code!

And for my last trick — Async Loops!

The other great feature of async functions, is that we can use awaitinside loops!

Using await in the for loop above makes sure that each quote gets printed out in sequence before the final console.log() is called.

The await keyword works great in both for and do loops, but NOT in Array.forEach() calls.

Summary

2018 is a great time to be a JavaScript developer — the language and platform is getting better and better. Async functions remove much of the complication that JavaScript developers have previously had to contend with, and with native support for them in NodeJS 8, server side JS development is now awesome too!

--

--

Russell Briggs

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