Speed up your code with Promise.all

So one of the things I see commented on all the time in Javascript code reviews.
Say you have some code as such…
1 2 3 |
<span class="k">async</span> <span class="kd">function</span> <span class="nx">myFunction</span><span class="p">()</span> <span class="p">{</span> <span class="k">await</span> <span class="nx">someThing</span><span class="p">();</span> <span class="k">await</span> <span class="nx">someOtherThing</span><span class="p">();</span> <span class="k">await</span> <span class="nx">yetAnotherThing</span><span class="p">();</span> <span class="p">}</span> |
Do we need to each of these one at a time? Cant we fork this and call them all together?
What being pointed out here is that we have to wait on each one of these Promises to return before the next one can start.
One of the most amazing things about working with Javascript is its concurrency. Meaning it can run your code in parallel instead of waiting for each Promise to finish before moving starting the next. Commonly referred to as “forking”.
Using Promise.all vs await each
Since this ability is so powerful, lets make sure we are using it. Let’s take a look at the example above and see how we can make that run in parallel.
1 2 3 4 5 |
<span class="kd">function</span> <span class="nx">delayedResponse</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span> <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">resolve</span><span class="p">,</span> <span class="mi">1000</span><span class="p">);</span> <span class="p">});</span> <span class="p">}</span> <span class="k">async</span> <span class="kd">function</span> <span class="nx">time</span><span class="p">(</span><span class="nx">label</span><span class="p">,</span> <span class="nx">fn</span><span class="p">)</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">start</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">();</span> <span class="k">await</span> <span class="nx">fn</span><span class="p">();</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span> <span class="p">(</span><span class="k">new</span> <span class="nb">Date</span><span class="p">()</span> <span class="o">-</span> <span class="nx">start</span><span class="p">)</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">,</span> <span class="s2">`seconds to load </span><span class="p">${</span><span class="nx">label</span><span class="p">}</span><span class="s2">`</span> <span class="p">);</span> <span class="p">}</span> <span class="nx">time</span><span class="p">(</span><span class="dl">"</span><span class="s2">sequential</span><span class="dl">"</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span> <span class="k">await</span> <span class="nx">delayedResponse</span><span class="p">();</span> <span class="k">await</span> <span class="nx">delayedResponse</span><span class="p">();</span> <span class="k">await</span> <span class="nx">delayedResponse</span><span class="p">();</span> <span class="p">});</span> <span class="nx">time</span><span class="p">(</span><span class="dl">"</span><span class="s2">parallel</span><span class="dl">"</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span> <span class="k">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">([</span> <span class="nx">delayedResponse</span><span class="p">(),</span> <span class="nx">delayedResponse</span><span class="p">(),</span> <span class="nx">delayedResponse</span><span class="p">()</span> <span class="p">]);</span> <span class="p">});</span> |
Ok, here is some code that we can test this idea with. I have a function that returns a Promise that takes 1sec to resolve.
1 2 3 |
<span class="kd">function</span> <span class="nx">delayedResponse</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span> <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">resolve</span><span class="p">,</span> <span class="mi">1000</span><span class="p">);</span> <span class="p">});</span> <span class="p">}</span> |
and a function that times how long a function passed to it takes to run.
1 2 3 |
<span class="k">async</span> <span class="kd">function</span> <span class="nx">time</span><span class="p">(</span><span class="nx">label</span><span class="p">,</span> <span class="nx">fn</span><span class="p">)</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">start</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">();</span> <span class="k">await</span> <span class="nx">fn</span><span class="p">();</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span> <span class="p">(</span><span class="k">new</span> <span class="nb">Date</span><span class="p">()</span> <span class="o">-</span> <span class="nx">start</span><span class="p">)</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">,</span> <span class="s2">`seconds to load </span><span class="p">${</span><span class="nx">label</span><span class="p">}</span><span class="s2">`</span> <span class="p">);</span> <span class="p">}</span> |
These are not important, I just wanted to explain what they were. The main thing that we are talking about here is the difference in speed with the two approaches we are timing here.
Ok lets run this code and see what happens.
1 2 3 |
1.002 seconds to load parallel 3.005 seconds to load sequential |
Whoah… that’s almost 3 times as fast for the parallel one. Makes sense, as there are three calls and each one takes a second. You can see how this would greatly effect your performance if you had many Promises to wait on.
Say for example we had 6 calls instead of 3. What would the timing on that look like?
1 2 3 |
1.002 seconds to load parallel 6.009 seconds to load sequential |
You guessed it, twice as much on the difference.
Real world example
Say for example you have a front end app that makes a few API requests before it’s ready to render and become interactive. This could make a HUGE difference to your users.
Capturing the responses
One of the common excuses I have seen for awaiting each Promise is that,
But I need to save each response from each Promise
1 2 3 |
<span class="k">async</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">myData</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">someApiCall</span><span class="p">();</span> <span class="c1">// do something with your data...</span> <span class="p">}</span> |
You can still do that with Promise.all
. Promise.all returns the results in an Array in the order that the Promises were invoked, not in the order that they resolve. This is a common misconception.
Ok lets test this idea.
1 2 3 4 5 6 7 8 |
<span class="kd">function</span> <span class="nx">randomDuration</span><span class="p">()</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">min</span> <span class="o">=</span> <span class="mi">500</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">max</span> <span class="o">=</span> <span class="mi">1000</span><span class="p">;</span> <span class="k">return</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="p">(</span><span class="nx">max</span> <span class="o">-</span> <span class="nx">min</span><span class="p">)</span> <span class="o">+</span> <span class="nx">min</span><span class="p">;</span> <span class="p">}</span> <span class="kd">function</span> <span class="nx">delayedResponse</span><span class="p">(</span><span class="nx">msg</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span> <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">randomDuration</span><span class="p">(),</span> <span class="nx">msg</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">: done</span><span class="dl">"</span><span class="p">);</span> <span class="p">});</span> <span class="p">}</span> <span class="k">async</span> <span class="kd">function</span> <span class="nx">time</span><span class="p">(</span><span class="nx">label</span><span class="p">,</span> <span class="nx">fn</span><span class="p">)</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">start</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">();</span> <span class="k">await</span> <span class="nx">fn</span><span class="p">();</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span> <span class="p">(</span><span class="k">new</span> <span class="nb">Date</span><span class="p">()</span> <span class="o">-</span> <span class="nx">start</span><span class="p">)</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">,</span> <span class="s2">`seconds to load </span><span class="p">${</span><span class="nx">label</span><span class="p">}</span><span class="s2">`</span> <span class="p">);</span> <span class="p">}</span> <span class="c1">// the code we are benchmarking</span> <span class="k">async</span> <span class="kd">function</span> <span class="nx">sequential</span><span class="p">()</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">res1</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">delayedResponse</span><span class="p">(</span><span class="dl">"</span><span class="s2">first</span><span class="dl">"</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">res2</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">delayedResponse</span><span class="p">(</span><span class="dl">"</span><span class="s2">second</span><span class="dl">"</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">res3</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">delayedResponse</span><span class="p">(</span><span class="dl">"</span><span class="s2">third</span><span class="dl">"</span><span class="p">);</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">([</span><span class="nx">res1</span><span class="p">,</span> <span class="nx">res2</span><span class="p">,</span> <span class="nx">res3</span><span class="p">]);</span> <span class="p">}</span> <span class="k">async</span> <span class="kd">function</span> <span class="nx">parallel</span><span class="p">()</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="k">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">([</span> <span class="nx">delayedResponse</span><span class="p">(</span><span class="dl">"</span><span class="s2">first</span><span class="dl">"</span><span class="p">),</span> <span class="nx">delayedResponse</span><span class="p">(</span><span class="dl">"</span><span class="s2">second</span><span class="dl">"</span><span class="p">),</span> <span class="nx">delayedResponse</span><span class="p">(</span><span class="dl">"</span><span class="s2">third</span><span class="dl">"</span><span class="p">),</span> <span class="p">]);</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">results</span><span class="p">);</span> <span class="p">}</span> <span class="nx">time</span><span class="p">(</span><span class="dl">"</span><span class="s2">sequential</span><span class="dl">"</span><span class="p">,</span> <span class="nx">sequential</span><span class="p">);</span> <span class="nx">time</span><span class="p">(</span><span class="dl">"</span><span class="s2">parallel</span><span class="dl">"</span><span class="p">,</span> <span class="nx">parallel</span><span class="p">);</span> |
Ok the main difference here is that I am giving each Promise a random duration before it returns, between 1/2 and 1 second. That way I can show that they come back in the order invoked, not the order resolved.
1 2 3 4 5 |
<span class="o">[</span> <span class="s1">'first: done'</span>, <span class="s1">'second: done'</span>, <span class="s1">'third: done'</span> <span class="o">]</span> 0.921 seconds to load parallel <span class="o">[</span> <span class="s1">'first: done'</span>, <span class="s1">'second: done'</span>, <span class="s1">'third: done'</span> <span class="o">]</span> 2.393 seconds to load sequential |
Notice that even though I randomized how long each Promise will take to resolve, the results array is in the order that I called them in. Fantastic.
Example where this won’t work
There are some cases where you cannot call all your promises at the same time. Take for example API calls like I mentioned above. Say you need part of the result from one, before you can call the next.
For example say you are using REST routes that are nested…
1 2 3 |
<span class="k">async</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Api</span><span class="p">.</span><span class="nx">getUser</span><span class="p">();</span> <span class="kd">const</span> <span class="nx">account</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Api</span><span class="p">.</span><span class="nx">getAccount</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">id</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">articles</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Api</span><span class="p">.</span><span class="nx">getArticles</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">id</span><span class="p">);</span> <span class="p">}</span> |
Well, you cant call all these together, but you may be able to still group some. Just do the best you can 🙂
1 2 3 |
<span class="k">async</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Api</span><span class="p">.</span><span class="nx">getUser</span><span class="p">();</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">account</span><span class="p">,</span> <span class="nx">articles</span><span class="p">]</span> <span class="o">=</span> <span class="k">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">([</span> <span class="nx">Api</span><span class="p">.</span><span class="nx">getAccount</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">id</span><span class="p">),</span> <span class="nx">Api</span><span class="p">.</span><span class="nx">getArticles</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">id</span><span class="p">)</span> <span class="p">]);</span> <span class="p">}</span> |
Promise.all vs Promise.allSettled
One very important thing to note, If ANY of the promises reject. Promise.all will reject at the first failure. Think of it as fail fast.
If you need to handle each rejection, and need a response for every Promise no matter what happens, use Promise.allSettled
instead of Promise.all
Source: https://dev.to/dperrymorrow/speed-up-your-code-with-promiseall-3d4i
