Back to blog

Simple async in JavaScript

Everyone has their favorite pattern for managing multiple asynchronous operations in JavaScript. Here are two techniques that have been working well for us at MapBox. They are simple, client/server compatible*, and I find them to be rather elegant. These patterns are easy for any JavaScript developer to understand without having to learn the nuances of a dedicated async library.

Both patterns more or less assume that you are following the Node.js style for passing errors to callbacks–e.g. callback(err, ...).

The first pattern carries out a series of operations (usually I/O) in order, which helps organize code and avoid indent hell. It is basically a function that calls itself recursively until all operations are complete. Here is an example:

We set up a function named load() that takes a results object and a callback function as arguments. It contains a series of checks that look for loaded data. If the data is missing we attempt to load it. When the async operation is complete we add the result to the results object and then call the load() function again. We repeat this process until all of the data is loaded and then finally call the main callback to do something with the gathered results. In this example, an error in any one of the operations will interrupt the process.

The second pattern is for running an asynchronous task in parallel and collecting errors and results along the way.

This time we set up a function that takes an items array and a callback as arguments. The function creates a loaded array with a length that matches that of the items array. We start the async operations and populate the loaded array as they complete. Upon each completion we check to see if all other operations are complete by looking at the number of truthy values in the loaded array. This technique ensures that the order of the items in the output array corresponds to the order of the items array regardless of actual completion order. The callback is called with the first error but all valid objects and other errors are also returned in the result array in case you want to do further analysis.

*In the browser you will want to use filter() or even compact() from underscore.js for compatibility.

Note that this pattern will start all of the async operations at once. If you are looking to throttle parallel async operations and handle error's take a look at basic-queue.

Maybe they aren't right for every situation, but after going through several phases of using dedicated libraries like async and step I prefer these for being lightweight and portable.

Credit for the conjuring of these patterns goes to Young Hahn.