Discover more from Stuff on Engineering
Some Beginner Tips for Concurrency with Async/Await and Promise.all
How you can make good use of concurrency to reduce execution time.
So you are using Async/Await and enjoying all the great benefits of it:
Better error handling
Easy Branching (if/else)
Yet it’s a good idea not to let the nice and clean syntax take your mind away from concurrency and how you can benefit from running tasks concurrently.
Concurrent computing is a form of computing in which several computations are executed during overlapping time periods — concurrently — instead of sequentially (one completing before the next starts).
I will show the benefits by presenting two cases where concurrency shines.
Case 1: List of tasks
Let’s say you have a list of posts that need to be published on an external service. Before publishing you want to validate some post fields and if they’re OK to publish.
The sequential solution 😴
You can iterate over the list and check validation for each post, then if the post is valid proceed and publish it.
This solution works, but you need to pay attention that each post has to wait until the end of the previous one to finish.
There is no reason why we should not execute all of them simultaneously.
The concurrent solution 😎
First we will create a publishing pipeline: validate → publish
Second, we will map all our posts to the pipeline and get back a list of tasks (Promises). Now we can call “Promise.all” to wait until all of the pipelines are done. Execution time has been reduced to almost one pipeline because each pipeline is running independently from one other.
If you are dealing with very long sequences of posts it’s a good idea to split the pipelines into chunks and execute them one by one.
Posting time: 200ms
Number of posts: 20
Seq solution: 200ms * 20 = 4sec
Concurrent solution: approximately 200ms
4sec vs 200ms - 20x faster
Case 2: Independent sources
Sometimes you relay on several independent data sources, same as having multiple arguments for a function. they are not blocking each other and can be fetched in concurrent.
Now let’s imagine you need to compare all users’ payments, invoices and receipts in your platform.
The sequential solution 😴
You can get each resource one by one and then use them.
But now each data source will be fetched only after the previous one is finished, causing a slower execution time.
A concurrent solution 😎
Create 3 different tasks and wait until all of them are done.
Here all of the calls are made in concurrent, which means we now have a faster preparation time, especially when calls are expensive in terms of time.
Get payments: 900ms
Get invoices: 800ms
Get receipts: 2000ms
Seq solution: 900ms + 800ms + 2000ms = 3700ms = 3.7s
Concurrent solution: Max(900ms, 800ms, 2000ms) = 2000ms = 2sec
3.7s vs 2sec - 1.85x faster
Async/Await can give us a lot of benefits in terms of readability and error handling, but we should not forget to use this feature wisely and always look for the tasks that could be handled concurrently.
Always try to think which calls are non blocking and which ones have to run one by one.
Every time you’re finding yourself writing a loop with await try to figure out if you can use Promise.all based on the dependency of the calls.