Start async operations, then await later
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.
Introduction
Starting asynchronous work and awaiting it later is a standard way to reduce wasted time in I/O-heavy code. The idea is simple: kick off operations as early as possible, do other useful work while they are in flight, and only await when you actually need the result. This pattern is especially valuable in JavaScript, where many independent network calls are accidentally serialized by overly eager await statements.
Why Early await Can Slow Code Down
await does not block the entire process, but it does pause the current async function until that specific promise settles. If you write a sequence of independent operations with await after each one, you force them to run one after another.
This is correct, but it serializes three unrelated requests. Total runtime becomes roughly the sum of all three latencies.
Start the Promises First
If the operations are independent, start them immediately and keep the promises.
Now the three requests overlap. The function still awaits all of them, but it waits later, after the requests have already started and after local CPU work has been done.
Use Promise.all When All Results Are Required
If you know you need every result, Promise.all is usually the cleanest form.
Promise.all fails fast, which is usually desirable when one missing dependency means the whole operation cannot complete. It also makes it obvious that these promises form one logical group.
Use Promise.allSettled for Partial Results
Sometimes one failure should not cancel everything. For example, a dashboard may still render user data even if a recommendation service is down.
This pattern keeps concurrency but changes the failure policy. That is the important distinction. Concurrency is about timing. all versus allSettled is about error semantics.
Do Not Start Dependent Work Too Early
The pattern only helps when tasks are genuinely independent. If request B needs the result of request A, starting B first is wrong.
This sequence is correct because the second URL depends on data from the first response. Trying to parallelize dependent steps usually produces invalid requests, placeholder values, or extra retry logic that is harder to maintain than the original code.
Watch for Unhandled Rejections
If you start a promise and wait a long time before observing it, be deliberate about how errors are handled. In Node.js, a rejected promise can become an unhandled rejection if nothing eventually consumes it in the expected way.
A practical guideline is:
- store the promise immediately in a clearly named variable
- await it in the same function whenever possible
- group related promises with
Promise.allorPromise.allSettled
That keeps lifetime and ownership obvious.
This Pattern Exists Outside JavaScript Too
The concept is broader than JavaScript. In C#, you can start Tasks and await them later with Task.WhenAll. In Python, you can create tasks with asyncio.create_task() and await them later. The principle is the same everywhere: launch independent I/O early, then synchronize only when the results are needed.
The key is not “make everything concurrent.” The key is “avoid accidental serialization.”
Common Pitfalls
- Writing independent async calls with immediate
awaitand unintentionally forcing serial execution. - Starting work early even though the later operation depends on data from the earlier one.
- Ignoring the failure behavior difference between
Promise.allandPromise.allSettled. - Kicking off promises and letting their ownership become unclear, which makes error handling fragile.
- Using concurrency for trivial local CPU work where there is no waiting time to hide.
Summary
- Start independent async operations as soon as possible and await them when their results are actually needed.
- Storing the returned promises lets I/O overlap instead of running sequentially.
- '
Promise.allis the usual choice when every result is required.' - '
Promise.allSettledis better when partial results are acceptable.' - The pattern improves performance only when the operations are independent and I/O-bound.
Related reading
- static destructor
- Statistic estimation of total nodes in a tree where edge traversal is expensive
- stl map performance?
- STL way to access more elements at the same time in a loop over a container
- Start multiple process and redirect output async
- Start task, later wait for completion
- Starting multiple async/await functions at once and handling them separately
- Static variables in JavaScript

DSA Fundamentals
Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.