Execute Program

JavaScript Concurrency: Running Promises Concurrently

Welcome to the Running Promises Concurrently lesson!

This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!

  • Imagine that we're writing a web application. It needs to make multiple requests to an API at once. In this lesson, we'll explore four different ways to make those requests.

  • To keep the examples simple, we'll use a fake API rather than a real one. We'll represent a "user" with a string ID so we can focus on the concurrency, not the details of the API. To start, here's a faked getUser function that waits for 500 ms to simulate network delay, then returns a promise containing a "user".

  • >
    function getUser(id) {
    return new Promise(resolve => {
    setTimeout(resolve, 500); // Simulate slow network request
    }).then(() => {
    console.log('got user', id);
    return 'user ' + id.toString();
    });
    }
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    getUser(1);
    Asynchronous Icon Async Result:
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    getUser(2);
    Asynchronous Icon Async Result:
    {fulfilled: 'user 2'}Pass Icon
  • Our first solution for requesting multiple users is: get the first user, then attach a then callback to get the second user. We can repeat that as many times as necessary.

  • (Remember: when a then returns a promise, the "outer" promise will resolve to the fulfilled value inside the "inner" promise.)

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    getUser(1).then(user1 =>
    getUser(2).then(user2 =>
    [user1, user2]
    )
    );
    Asynchronous Icon Async Result:
  • That works, but the performance isn't great. Our second API call can't begin until the first API call finishes. We can see that in the console output: each API request takes about 500 ms, but the total time is about 1000 ms. The CPU is idle for almost all of that time; we're only waiting for the network (or, in our case, for the timer that simulates the network).

  • Our second solution is to begin both API calls at the same time, then create a new promise waiting for them to return data. Creating a promise schedules it to run, whether or not we attach a then, so both API requests begin immediately. This makes a significant impact on the amount of time it takes to get our data. It cuts our total time from around 1000 ms to 500 ms!

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    const promise1 = getUser(1);
    const promise2 = getUser(2);

    promise1.then(user1 =>
    promise2.then(user2 =>
    [user1, user2]
    )
    );
    Asynchronous Icon Async Result:
    {fulfilled: ['user 1', 'user 2']}Pass Icon
  • But what if we need to make seven API requests instead of two? Nesting seven then calls doesn't seem like a good solution.

  • Our third solution is to write a waitForAll function. It takes any number of promises, collects their fulfilled values into an array, and returns a new promise that fulfills with that array. This example is a bit more complex, and the exact implementation here isn't very important.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    function waitForAll(promises) {
    // Start with an empty array.
    let arrayPromise = Promise.resolve([]);

    for (const promise of promises) {
    /* Build a new promise holding an array that contains all of the old
    * array elements, plus the one from this promise. */
    arrayPromise = arrayPromise.then(array =>
    promise.then(value =>
    [...array, value]
    )
    );
    }

    return arrayPromise;
    }

    /* Once the `waitForAll` function is written, waiting for multiple API
    * calls is easy! */
    waitForAll([getUser(1), getUser(2), getUser(3)]);
    Asynchronous Icon Async Result:
  • waitForAll seems to do the job: it returns an array in 500 ms, not the 1500 ms that sequential requests would take. And there's more good news: there's no need to write that function because JavaScript comes with it!

  • This brings us to our fourth solution: the built-in Promise.all. This function deserves its own lesson, and we'll explore it in detail soon. In the meantime, you can see that it works like our waitForAll function above.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    Promise.all([getUser(1), getUser(2), getUser(3), getUser(4)]);
    Asynchronous Icon Async Result: