tutorial // Jul 15, 2022

How to Use Promise.all() to Wait On Multiple Promises

How to use Promise.all() to wait on an array of Promise calls to resolve before executing more code.

How to Use Promise.all() to Wait On Multiple Promises

For this tutorial, we're going to use CheatCode's full-stack JavaScript framework, Joystick. Joystick brings together a front-end UI framework with a Node.js back-end for building apps.

To begin, we'll want to install Joystick via NPM. Make sure you're using Node.js 16+ before installing to ensure compatibility (give this tutorial a read first if you need to learn how to install Node.js or run multiple versions on your computer):

Terminal

npm i -g @joystick.js/cli

This will install Joystick globally on your computer. Once installed, next, let's create a fresh project:

Terminal

joystick create app

After a few seconds, you will see a message logged out to cd into your new project and run joystick start:

Terminal

cd app && joystick start

After this, your app should be running and we're ready to get started.

Writing a test Promise

While we can technically use any JavaScript Promise for our test, to keep things simple, we're going to begin by wiring up a very simple Promise: a function that takes a timeout as timeoutInSeconds and resolves a Promise returned by the function in a setTimeout() after the timeout completes.

/lib/wait.js

export default (timeoutInSeconds = 0) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`Completed after ${timeoutInSeconds} seconds!`);
    }, timeoutInSeconds * 1000);
  });
};

Here, inside of the /lib folder that was created for us when we ran joystick create app above, we create a new file wait.js and in it, add an export default of a function which takes in timeoutInSeconds as its only argument (the = 0 part after the argument name is us setting a default of 0 in case no value is passed).

Inside of that function, we return a new Promise(). To that new Promise() instance, we pass a callback function to define the behavior of our Promise. Like we hinted at above, we want to run a setTimeout(), using the timeoutInSeconds passed to us as the delay for the setTimeout().

Inside of the callback for that setTimeout(), after the timeoutInSeconds has completed, we call to the resolve function passed to us when we call new Promise(). To it, we pass a string acknowledging that we completed the requested "wait" so that we get a return value when calling the Promise via Promise.all().

That's it for our test Promise. Again, you could use any function returning a Promise for the next step (e.g., a call to fetch() or some third-party library function that returns a Promise).

Using Promise.all()

Now, to see how Promise.all() works, we're going to create a simple component in our app using the @joystick.js/ui package built-in to the framework we're using for this tutorial. To keep things simple, we're going to open up the file at/ui/pages/index/index.js which was created for us when we ran joystick create app earlier. To start, we're going to replace the contents of this file with the following template:

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  render: ({ state }) => {
    return `
      <div></div>
    `;
  },
});

export default Index;

This will give us a blank slate component to test our Promise.all() call from. Next, let's modify this component to add our test code in (pulling our /lib/wait.js function at the top of the file) and walk through how it's working.

/ui/pages/index/index.js

import ui from '@joystick.js/ui';
import wait from '../../../lib/wait';

const Index = ui.component({
  state: {
    running: false,
  },
  events: {
    'click button': (_event, component) => {
      component.setState({ running: true }, async () => {
        const results = await Promise.all([
          wait(1),
          wait(2),
          wait(4),
          wait(8),
        ]);
  
        console.log(results);
        component.setState({ running: false });
      });
    },
  },
  render: ({ state }) => {
    return `
      <div>
        <button ${state?.running ? 'disabled' : ''}>${!state?.running ? 'Start the Promise chain' : 'Running...'}</button>
      </div>
    `;
  },
});

export default Index;

Starting down in the render(), we've added a <button></button> that we can click to start our Promise chain. To decide whether or not it's disabled (and what it's current label reads), we "pluck off" the component's current state value by destructuring the component instance object passed to render().

If the running value is true, we want to mark the button as disabled so it's not clickable and change its label to "Running..." If we look near the top of our call to ui.component(), the first property we set on the options object passed to this function (the definition for our component) is an object state and inside a property running being set to false. This is us setting the default state value for the component when it loads up in the browser (i.e., the button will not be disabled when the page loads).

Next, up in the events object being defined beneath state, we add an event listener for a click event on our <button></button> with click button. To that property, we pass a function to call when a click is detected on the button. Inside, using the component instance passed as the second argument to the event handler (after the DOM event object itself which we can disregard here), we call to the component's .setState() method to set running to true.

After this, we pass a callback function (note the prefixing of the async keyword on this function) to call after the setState() call has completed. Inside, we create a variable const results which is assigned a call to await Promise.all(). To Promise.all(), we're passing an array of calls to our wait() function which we've imported at the top of the file.

Remember: our wait() function receives an integer representing a timeoutInSeconds we want our function to wait for before resolving. Because our goal here is to demonstrate a call to Promise.all(), we want to call wait() multiple times with varying timeouts. This will demonstrate real-world delays from other Promise-based functions that we need to wait on before completing some task. Here, we expect the Promise.all() call to not resolve until all of the Promises passed to it resolve.

In other words, we expect to click the button and have a delay of eight (8) seconds until Promise.all() resolves, and stores its result in the results variable. The idea here being that, even though we have a console.log() of results and another call to component.setState() to enable our <button></button> again, we don't expect those to be called until Promise.all() resolves after 8 seconds.

If we load this up in a browser and click our button, we should see this exact outcome, with results containing an array of strings, each one representing the return value passed to resolve() inside of our wait() function.

Wrapping up

In this tutorial, we learned how to use Promise.all() to wait on an array of Promises. We learned how to define a simple JavaScript Promise that waits for some timeoutInSeconds to complete before resolving, and then, inside of a Joystick component, how to wire up an event listener to call Promise.all()—passing multiple wait() calls with varying timeouts to it—waiting for it to resolve and give us back the results of each call.

Written By
Ryan Glover

Ryan Glover

CEO/CTO @ CheatCode