The Power of Javascript Promise.all()

This post is not about Promises, async/await, or the single-threaded nature of Javascript. If you want to know about these topics, go here, here, or here.

This article is about the function Promise.all() and how you bring your independently running functions or tasks together into a beautiful result.

As is often the case, an example or use case does wonders.

Use Case: Post to Multiple Social Media Networks

Our app Ayrshare’s primary purpose is to provide one API to post across multiple social media networks, such as Twitter, Instagram, Facebook, and LinkedIn.

A user will make an API call (RESTful or via a client package) with the post text, image, and the list of networks to send the post. The API returns the resulting success or error for each network as an array of results.

However, there is complexity behind the scenes.

Each network has its own authorization, workflow, and security requirements. For example, if you attach an image to the post, Twitter requires you to first upload the image, wait for their processing to complete, and send the returned media ID in your a new update status call to Twitter. Timings vary, errors can occur for some and not for others, and new networks are added all the time. But from an end-user perspective, they sent the post and the results are returned right away.

Here is a visual (Firebase tech stack overview):

Javascript Promise.all

We want to run all the network posts in parallel and collect the results to return to the caller. As the number of networks increases, we ideally want to take no longer than the longest network.

The problem we face is if each network function call is asynchronous, then each function will complete at different times and we won’t be able to return the results in one response.

Promise.all() To The Rescue

If you have a similar problem, Promise.all() is your solution.

Promise.all() “takes an iterable of promises as an input, and returns a single Promise that resolves to an array of the results of the input promises.”

In other words, if you call several async functions, you can wait until they all resolve and capture their output in an array.

Let’s see a simple example:

const getSquare = async (x) => Math.pow(x, 2);
const printSquares = async () => {
     const nums = [1, 2, 3, 4, 5];
     const promiseArray = nums.map(x => getSquare(x));

     console.log(promiseArray);
};

printSquares();

printSquare iterates over an array of ints and squares each one. Calling getSquare returns a promise because we made it an asynchronous function by adding the keyword async – technically we don’t need async since the Math.pow function is synchronous.

The promiseArray is an array of promises, waiting to be resolved. When we print the promiseArray:

[
   Promise { 1 },
   Promise { 4 },
   Promise { 9 },
   Promise { 16 },
   Promise { 25 }
]

Close, but what is with those “Promises”? Well, as mentioned above, the array is composed of Promises waiting to be resolved. They can easily be resolved with Promise.all():

const getSquare = async (x) => Math.pow(x, 2);
const printSquares = async () => {
     const nums = [1, 2, 3, 4, 5];
     const promiseArray = nums.map(x => getSquare(x));

     const results = await Promise.all(promiseArray);
     console.log(results);
};

printSquares();

and the results:

[ 1, 4, 9, 16, 25 ]

Perfect! Also the results order is maintained.

Notice that we need to await on Promise.all() since itself returns a Promise that needs to be resolved.

For those that are curious, here is a real example of Ayrshare’s code that posts to all the networks:

/**
* Post to social networks
*/
const postNetworks = NETWORKS.map((net) => {
  return platforms.includes(net.type)
    ? net.func(auth[net.type], post, urls, id, {...})
    : [];
});

const processedSocial = await Promise.all(postNetworks);

Final Thoughts: Parallel Processing

Often Promise.all() is thought of as running in parallel, but this isn’t the case.

Parallel means that you do many things at the same time on multiple threads.

However, Javascript is single threaded with one call stack and one memory heap. It is an asynchronous, non-blocking language. This means Javascript doesn’t run in parallel, but rather runs only one function/Promise at a time. If the single thread has to wait on something, like the return from an http call, it will move on to another function until the return is complete.

In the case of our array of Promises, each Promise will be handled one at at time, but Javascript will switch between each one if the processing needs to wait. While the order of Promise resolution in Promise.all() can vary depending upon blocking, the final result will be an array of ordered results.