The async and await keywords are a great addition to Javascript. They make it easier to read (and write) code that runs asynchronously. That includes things like:

  • API calls (using fetch or a library like axios);
  • Timeouts (though these need a little extra work); or
  • Reading/writing files if you’re using NodeJS.

I’m going to assume you’re familiar with Promises and making a simple async/await call. Where it can get tricky is when we have a whole bunch of tasks in an array. You might run through the array expecting calls to run one-by-one. But instead, they run through all together and don’t wait for each individual item to resolve. Or, you might find yourself with the opposite problem. You run through your array in a loop, wanting them to run in parallel. But instead, it waits and does them one by one. And sometimes, you try something and it doesn’t work at all. The program just continues after the loop instead of waiting until the calls are all done. You end up wondering if there’s something you’re missing.

Now, if you’re feeling like that, take heart. Asynchronous programming is hard. Anyone who tells you differently is either lying or selling something. But there are some simple patterns you can learn that will make life easier. Let’s come up with a sample problem and solve it first in parallel, and then sequentially.

A sample problem

Let’s imagine we need to make a series of asynchronous function calls. They could be anything. As an example, we’ll make up a couple of different types. We’ll create one function that will delay for some time, then tell us how long it waited. And we’ll create another function that will fetch some text from another website.

function asyncTimeout(delay) {
    return (new Promise(resolve => {setTimeout(() => resolve(delay), delay)}))
        .then(d => `Waited ${d} seconds`);
}

function asyncFetch(url) {
    return fetch(url)
        .then(response => (response.text()))
        .then(text => `Fetched ${url}, and got back ${text}` );
}

Note that both of them return a Promise. In this case, they both return a Promise for a String.

Now let’s imagine that we have an array of asynchronous things we want to do:

const asyncThingsToDo = [
    {task: 'wait', duration: 1000},
    {task: 'fetch', url: 'https://httpstat.us/200'},
    {task: 'wait', duration: 2000},
    {task: 'fetch', url: 'https://urlecho.appspot.com/echo?body=Awesome!'},
];

And we create a function that reads the task specification, and calls the appropriate async function:

function runTask(spec) {
    return (spec.task === 'wait')
        ? asyncTimeout(spec.duration)
        : asyncFetch(spec.url);
}

Let’s see how we’d run these tasks in parallel and in sequence.

A parallel solution

Kicking off our tasks in parallel is the easy bit. All we need to do is not add an await when we make the function call. The slightly trickier part is gathering all the results back up once they’re all finished. For that we use a built in utility Promise.all(). Here’s how the code might look:

const tasks   = asyncThingsToDo.map(runTask); // Run all our tasks in parallel.
const results = await Promise.all(tasks);     // Gather up the results.
results.forEach(x => console.log(x));         // Print them out on the console.

Or, if we were to draw it as a diagram, it might look like this:

Flow diagram showing .map() running promises in parallel, then Promise.all() collecting them back up again.
We use .map() to kick of all our promises in parallel, then Promise.all() collects the results back up again for us.

We map over our list of things to do and call runTask() on each one. That creates an array of Promises, which we store in tasks. Then we use Promise.all() to convert that array of Promises into a single Promise. We use await to wait for this mega-Promise to resolve. And then finally, we call .forEach() to print the results to the console.

A sequential solution

Running our tasks in sequence is just a little more convoluted than running in parallel. The big idea is that we use .reduce() instead of .map(). Each time around we add another task onto a chain of promises with .then(). Here’s how it might look:

const starterPromise = Promise.resolve(null);
const log            = result => console.log(result);
await asyncThingsToDo.reduce(
    (p, spec) => p.then(() => runTask(spec).then(log)),
    starterPromise
);

If we were to draw that in a diagram, it might look something like this:

Flow diagram showing four promises run in sequence, each one followed by a call to the log function.
We use .reduce() to start the chain with a starter promise that does nothing. Then each promise resolves in turn, followed by a call to log().

We start by creating a Promise that doesn’t do anything. Then we use that as the initial value for .reduce(). Each time around the loop, we add another promise to the chain using .then(). And this time we log the result immediately instead of waiting for everything to finish. That way we get to see the result straight away.


So if you get stuck trying to run a bunch of asynchronous functions, maybe give one of these patterns a try. Here’s a way to approach it. The first thing to ask yourself is: “Do I want to run these functions in parallel, or in sequence?” If you know the answer to that question, then it’s easy to pick a pattern to make that happen:

  • If you want to run than in parallel, then we use .map() to kick everything off, then Promise.all() to gather them back up again.
  • If you want to run them sequentially, create a fake Promise to start then chain. Then call .reduce() to run each promise run in sequence.

Also, if you need to brush up on Promises, check one of these articles:

Or to find out more about async and await:

If you found any of this helpful, or if you found it confusing, please send me a tweet and let me know. I’d love to hear from you. And if you haven’t done so already, get your copy of the Civilised Guide to JavaScript Array Methods