JavaScript Async/Await — Sequential vs Parallel?
This is a daily Javascript challenge from the CodeShot archive. Practice your knowledge of Promise All Vs Sequential Await and improve your technical interview readiness.
// Option A: Sequential
const a = await fetchA()
const b = await fetchB()
const c = await fetchC()
// Option B: Parallel
const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()])
Detailed Explanation
Why This Question Matters
If you've spent any time with async/await, you know it makes asynchronous code look and feel like synchronous code. That's the whole point—it cleans up the "callback hell" and makes the logic easier to follow. But there is a hidden trap here.
Because await makes things look sequential, many developers accidentally write code that executes in a "waterfall" pattern. They wait for one request to finish before starting the next, even when the requests have nothing to do with each other. This is a silent performance killer. Your code still works, your tests pass, but your users are staring at a loading spinner for three seconds when it should have taken one.
Understanding the Code
Let's look at the two patterns.
Option A: The Sequential Approach
Here, JavaScript hits the first
await and pauses the execution of the function until fetchA resolves. Only then does it move to the next line to start fetchB. It’s a strict line. If each fetch takes 1 second, this block takes 3 seconds total.
Option B: The Parallel Approach
This is fundamentally different. When you call
fetchA(), fetchB(), and fetchC() inside the array, you are actually invoking those functions immediately. They all start their network requests at roughly the same time.
Promise.all then takes those three "pending" promises and wraps them into a single promise. The await keyword now waits for that *single* wrapper promise to resolve. If each fetch takes 1 second, the whole operation takes roughly 1 second (plus a tiny bit of overhead).
Finding the Correct Answer
The winner is Option B.
The logic is simple: why wait for a response from the User Profile API before you even *ask* the Permissions API for data? If fetchB doesn't need the result of fetchA to function, there is zero reason to run them sequentially.
By using Promise.all, you're leveraging the event loop's ability to handle multiple I/O operations concurrently. You aren't actually running the code in parallel threads (since JS is single-threaded), but you are allowing the browser or Node.js runtime to handle the network requests in the background simultaneously.
Common Mistakes Developers Make
The biggest mistake is assuming Promise.all is a silver bullet. There are two major "gotchas" you need to know:
1. The "All or Nothing" Failure
Promise.all is fail-fast. If any one of the promises rejects, the entire call rejects immediately. If fetchA and fetchB succeed but fetchC fails, you lose the data from A and B.
If you need the results of the successful requests regardless of whether others failed, use Promise.allSettled(). It returns an array of objects describing the outcome of each promise, allowing you to filter out the errors manually.
2. Over-fetching (The Throttling Risk)
While parallelizing is great, doing it 100 times at once is a bad idea. If you map an array of 500 IDs to fetch calls and wrap them in Promise.all, you might hit API rate limits or choke the browser's maximum concurrent connection limit. In those cases, you need a concurrency limit (using a library like p-limit).
Real-World Usage
In a production environment, you'll see this most often in "Dashboard" views.
Imagine a user profile page. You need:
- User basic info
- User's recent posts
- User's followers count
These are three separate API endpoints. If you await them one by one, the page feels sluggish. By using Promise.all, you trigger all three requests the moment the component mounts.
Another common pattern is using this for "batching" database queries. If you're fetching a list of orders and then need to fetch the product details for each order, don't put the product fetch inside a for loop with an await. Instead, map the orders to an array of promises and await them all at once.
Key Takeaways
- Stop the waterfall. If your async calls don't depend on each other, don't await them sequentially.
- Start the work first. Call your async functions to start the process, then use Promise.all to wait for the collective result.
- Watch for failures. Remember that Promise.all crashes if one promise fails. Use Promise.allSettled if you need a more resilient approach.
- Be mindful of scale. Parallelism is fast, but slamming a server with 100 simultaneous requests is a great way to get your IP blocked.
Why this matters
Understanding Promise All Vs Sequential Await is crucial for passing technical interviews. In real-world applications, this concept often leads to subtle bugs if not handled correctly. For more details, you can always refer to the official MDN Documentation.