Advanced Asynchronous Programming in JavaScript

Nicolas Vanhoren
Jan 5 · 6 min read

As programmers we all face it one day or another: asynchronous programming (A.K.A. non-blocking programming) ain’t easy. Fortunately JavaScript is one of the few programming languages where it’s actually nicely implemented (since a few years at least). That’s why I propose you today to see some more advanced techniques than simply sending an HTTP request and waiting for the response.

Image for post
Image for post
Photo by Markus Spiske on Unsplash

This article is for JavaScript programmers that have some understanding of promises and the async/await syntax and would like to know more, if you have trouble understanding it I can recommend you Mozilla’s course. It contains examples using the modern-async open source library. Also you might want to know I’m the creator of that library so it could have impact on my point of view.

Making multiple asynchronous operations

Let’s start simple by supposing we have some ubiquitous operation called :

async function doSomething () {
// we don't care what's in here

We don’t really care about what it does. It might be querying a REST API, waiting for the user to perform an operation, whatever. The only thing we know is that it’s an asynchronous function and we will have to use the keyword when calling it.

Calling it once is easy. But what if we need to call it n times ?

We can simply use a loop to do so:

for (let i = 0; i < 10; i += 1) {
await doSomething()

Wonderful! We just coded an algorithm that performs multiple asynchronous calls in a sequential way. Each asynchronous operation will be executed one after the other.

That algorithm will work flawlessly. But what if we don’t want to wait for each asynchronous operation to finish? After all, if the only thing we have to do is to wait, we could just as well perform multiple calls in parallel.

There is a well known pattern to do so:

const promises = []
for (let i = 0; i < 10; i += 1) {
await Promises.all(promises)

Here the little subtlety is that we don’t use directly when calling . Instead we gather the objects that function returns (because all async functions return promises) and we use the function to gather our list of promises in a single one that will be resolved when all the sub-promises are resolved. We can then use await on that “super-promise”.

OK that works too. Every operation will perform parallel to the others. But what if we don’t have a code example performing dumb operations while counting from 0 to 9? What if that’s a real world use case that performs ressource-consuming REST queries while iterating an array filled by a user that could contain hundreds or thousands of elements? Do we really want to have multiple thousands REST queries in parallel? Actually that won’t happen because your browser will plainly refuse to perform too many parallel HTTP calls and will crash your code instead.

So we know how to make many operations sequentially and we know how to make an infinity of parallel operations. The only thing we miss is the ability to perform many parallel operations with a limited concurrency.

Unfortunately that’s when we hit the limits of the basic API. We will still use it as well as of course, because they’re wonderful. But we need a little bit of help on top of those to make more advanced stuff. That’s why I’ll use the modern-async library:

import { forEachLimit } from 'modern-async'...const array = ... // our arrayawait forEachLimit(array, doSomething, 5)

is similar to the method. The only differences are that:

  • It’s an async function that takes an async callback. It will return when all the asked asynchronous operations have been performed.
  • It will call its asynchronous callback multiple times in parallel while taking care to not exceed the concurrency limit (here 5).

And that’s how we can optimize our code to perform multiple parallel operations while avoiding to explose the limits or our ressources screaming “the sky is the limit!”. (… and figuring it’s not)

More asynchronous operations on collections

We’ve seen that the modern-async library contains a function named similar to . But there are a lot of other generic list manipulation functions that are useful like or .

Fortunately that library contains asynchronous alternatives for all these functions. Here is a list of them:

All these functions also have shorthand equivalents to make common use cases shorter. In the example of we also have (a purely sequential alternative) and (a purely parallel alternative with no limit on the concurrency).

These functions should cover most needs regarding collections manipulation and asynchronous programming.

What about waiting?

Have you ever thought the function is boring to call nowadays? Let’s take a look at a minimal call to it:

setTimeout(() => {
... // your code
}, 300)

It’s 3 lines for a bare minimum call and you get the downside of calling a new function. It feels kind of old-school and callbacky when you are used to and doesn’t fit nicely with that syntax.

also contains a helper named sleep() for that:

import { sleep } from 'modern-async'await sleep(300)

It’s not much but it’s always useful to have an alternative that fits correctly in the paradigm.

Easy timeouts

Back to our function. What happens if we would like to impose a maximum amount of time for it to return?

We could make our own solution using or the function we just saw, but it won’t be so trivial to code.

That’s why provides a function for that whose name is, without much surprise, :

import { timeout } from 'modern-async'await timeout(doSomething, 5000)

This call will perform the exact same operation than with the exception that the asynchronous operation will be limited to 5 seconds (5000 milliseconds). If that delay is exceeded it will throw a instead.

The key to control the concurrency of anything — The ubiquitous Message Queue

Previously in this article we’ve seen how to limit the concurrency of asynchronous operations performed on collections. Let’s call that “easy mode”, because you don’t always have a nice list of operations in advance in the real world. There are cases where asynchronous operations could trigger any time (as example when the user performs an action).

The best generic solution to limit concurrency in these cases is named a Message Queue. modern-async provides an implementation of that paradigm in the class. Actually all the previous tools we’ve seen that allow to limit concurrency are just wrappers on top of it.

Here’s an example usage:

import { Queue } from 'modern-async'const myQueue = new Queue(3) // we will limit the concurrency to 3async function buttonClickHandler () { // let's image this function
// is called when the user clicks a button
const result = await myQueue.exec(doSomething)
// do something with the result if you want

Here we first create a object that we will assume to be global to our application. Whenever the user clicks a button an asynchronous call to is scheduled in the queue. If the concurrency is not yet reached it will be started immediately. If not it will be executed once at least one slot is freed (which means the previously scheduled asynchronous operations have completed). That way our user will be able to click on our button Cookie Clicker-style as much as he wants, it just won’t crash.

also provides a priority mechanism which allows to put some tasks before others in the queue.

The final word

As I said, asynchronous programming is not easy. Too many programmers ignore the subtleties of it and that can often lead to unstable programs. As “advanced” as this article may be branded it is based on real world needs that I encountered in my day-to-day work as a frontend and backend developer while working on very conventional applications. These use cases are not that rare and I think more developers should be aware of the solutions that exist to solve them.

You may also wonder why I provided examples using the modern-async library, which I created myself. Actually there exists more popular libraries for that like Async.js. Unfortunately I was quite frustrated when trying to use these libraries as most of them are quite old-school and do not fit well in the newer paradigm. Since I’m not very willing to go back to the callback-hell coding style I took time to make a good open-source project that solve my problem. If you have some time a star ⭐on Github would be welcome 😀.

JavaScript In Plain English

New JavaScript + Web Development articles every day.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store