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.
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.