Resolved JavaScript Promises Can Be Used Multiple Times!

When developers are first learning JavaScript Promises, it's easy to think "Okay, I create a promise, and I 'dot then' it to write the code that will run when the Promise resolves."  It is possible to be quite successful developing JavaScript code with this mindset, gaining a deeper understanding of Promise can lead to realizing rather simple ways to solve seemingly difficult problems.


A JavaScript Promise is more than just an object with a then function. It is, at its core, an object that represents some work to be run asynchronously. When creating a Promise, you pass its constructor an "executor" function with the asynchronous code in it. This executor function receives two functions as parameters. These parameters are canonically called resolve and reject. One of these two functions must be called to let the Promise know that the executor functions has completed it's work. It is not enough for the executor function to finish running, as the executor function may itself call out to some asynchronous code.

Any value passed resolve will be passed as a parameter to the function passed to the .then function of the Promise. For this post, I am going to ignore what happens to values passed to reject.

Let's create a simple Promise and run it in our browser's console.

const myPromise = new Promise( (resolve, reject) => {
  resolve('Hello world!');
});

myPromise.then( value => console.log(value));

I realize this code doesn't actually do anything asynchronous, but the code passed to the Promise does run asynchronously. The important thing for this post is that we are not limited to calling .then just once on a Promise. We can call it multiple times if we wish. Let's see what happens if we do just that.

This is interesting! We get the same result from the Promise every time we call .then. But again, the work in our Promise isn't asynchronous, and I could just as easily have written this:

console.log('Hello world');
console.log('Hello world');
console.log('Hello world');

But again, a Promise is an object that represents work! Let's say that instead of just logging to the console, I am using the fetch API (Promised-based API for making HTTP calls) to reach out to the GitHub user API to get a list of users. I need to utilize this list of users multiple times in my application.

A simple way to accomplish this would be to call the API every time I need the list of users.

fetch('https://api.github.com/users')
  .then( response => response.json())
  .then( users => console.log( users[0].login ));

fetch('https://api.github.com/users')
  .then( response => response.json())
  .then( users => console.log( users[1].login ));

fetch('https://api.github.com/users')
  .then( response => response.json())
  .then( users => console.log( users[2].login )); 

This is what our Network activity will look like:

Of course, this means we will likely quickly hit up against API rate limits as we are wasting bandwidth. Wouldn't it be easier to hold on the results of this API call? Let's only call the API once and then store the results in a local variable. Then we can log out the results later, no problem!  But there's a problem when we run this code.

It seems users is undefined. This is because the console.log call runs immediately after fetch is called. It does not wait for fetch to finish making the call to GitHub. We can solve this by calling then on the Promise created by calling fetch, like this:

Everything works great! We also are only making one call to the GitHub API:

But there's still a problem. Remember the requirement "I need to utilize this list of users multiple times in my application." We're only able to utilize this list of users once with how this code is written. But wait! I can utilize the strategy I learned earlier of calling .then repeatedly on a Promise object. This means I can utilize the Promise object to "cache" the result of this GitHub API call and use this as much as I want in my application. This means we need to save the Promise as a variable.

const fetchUsers = fetch('https://api.github.com/users')
                     .then( response => response.json());

We can now use the resolved value of this Promise repeatedly by calling .then repeatedly:

So how can we use this in a "real" application? Maybe you have a button in your application that will display user information when clicked. You could wait to make the fetch call until the button is clicked, and then save this Promise in a variable. In future calls, the code will see that this Promise already exists, and thus skip calling fetch again. Something like this:

let fetchUsers;   

document.getElementById('displayUser').onclick = () => {
  if(!fetchUsers) {
    fetchUsers = fetch('https://api.github.com/users')
      .then( response => response.json());
  } 
  
  const index = document.getElementById('index').value;
  
  fetchUsers.then(users => {
    document.getElementById('userName').innerHTML = users[index].login;
  });
};                   

I have set up a GitHub gist with this code, and it is runnable using gist.run here: https://gist.run/?id=8f7cc057e31f94269061c9bf4789e7e2

Go run the Gist, open your developer tools, go to the network tab, note that the call to the GitHub API does not happen until you click the "Display nth User" for the first time, and that it is not called again after the first click (unless you reload the gist). Feel free to play with the code in gist.run!


JavaScript Promises are powerful, and they become more powerful once we understand that a "resolved" Promise can be used more than once!