DEV Community

Cover image for Some Beginner Tips for Concurrency with Async/Await and Promise.all
Shy Alter
Shy Alter

Posted on • Originally published at Medium on

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:

  1. Clean Code
  2. Better error handling
  3. Easy Branching (if/else)
  4. Debugging
  5. Much more…

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.


Multitasking

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.

async function publishAll() {
// You get the posts
const posts = await getPosts();
const successes = [];
const failures = [];
for (const post of posts) {
// synchronous check for validtion
if (!validate(post)) {
failures.push({ post, status: "failed" });
} else {
// publish it to the 3rd party api
try {
await publish(post);
results.push({ post, status: "published" });
} catch (e) {
failures.push({ post, status: "failed", error: e });
}
}
}
return {
successes,
failures
};
}

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.

async function publishPipeline(post){
if (!validate(post)){
return { post, status: "failed" };
}
try {
await publish(post)
return { post, status: "published" };
} catch (e) {
return { post, status: "failed", error: e };
}
}
async function publishAll() {
const posts = await getPosts();
const publishTasks = posts.map(publishPipeline);
const results = await Promise.all(publishTasks);
const successes = results.filter(post => post.status === "published");
const failures = results.filter(post => post.status === "failed");
return {
successes,
failures
};
}

Posting time: 200ms

Number of posts: 20

Seq solution: 200ms * 20 = 4sec 

Concurrent solution: approximately 200ms

4sec vs 200ms - **20x faster**
Enter fullscreen mode Exit fullscreen mode

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.

async function publishAll() {
const payments = await getPayments();
const invoices = await getInvoices();
const receipts = await getReceipts();
// do stuff with that
}

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.

async function publishAll() {
const [payments, invoices, receipts] = await Promise.all([
getPayments(),
getInvoices(),
getReceipts()
]);
// do stuff with that
}

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**
Enter fullscreen mode Exit fullscreen mode

Conclusion

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.

Top comments (0)

nextjs tutorial video

Youtube Tutorial Series

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series 👀

Watch the Youtube series

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay