Waiting until two async blocks are executed before starting another block
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
In JavaScript, a frequent requirement is to wait for two independent asynchronous operations before starting a third one. The reliable solution is to compose promises explicitly rather than coordinating with shared flags or timing assumptions. When this pattern is done well, code becomes easier to reason about and failures are handled in one place.
Use Promise Composition Instead Of Manual State
The most direct pattern is Promise.all. It starts all input promises, waits until both resolve, and then passes their results to the next step. If any promise rejects, the combined promise rejects immediately.
This is deterministic: the final block runs only after both upstream blocks are complete.
Handling Partial Failures Safely
Some workflows should continue even when one async task fails. In that case, use Promise.allSettled and inspect statuses.
This strategy avoids aborting non critical flows while still enforcing requirements for critical data.
Controlling Sequence And Parallelism
A common mistake is writing sequential awaits when tasks are independent. That increases latency because the second task starts only after the first one ends.
Less efficient pattern:
Better parallel pattern:
If you have many tasks and need throttling, combine a queue library or worker pool with this pattern. Start only a controlled number of concurrent operations, then aggregate completion with promise composition.
Testing The Flow
Write tests that verify both success and failure paths. For async orchestration, tests should assert execution order and rejection behavior.
Tests prevent regressions when a refactor changes promise wiring.
Cancellation, Timeouts, And Cleanup
Production systems often need a timeout guard so one slow dependency does not block the entire workflow forever. You can race the combined promise against a timer and surface a controlled error when the deadline is exceeded. This keeps the orchestration predictable under network instability.
When side effects are involved, define cleanup behavior before you launch concurrent work. For example, if one request writes temporary state and the second request fails, you may need a compensating action. Promise composition handles waiting, but consistency rules are still your responsibility.
Also keep concurrency boundaries explicit. A helper that always returns a promise and never mutates shared external state is easier to compose safely. If shared state is required, isolate writes to one stage after Promise.all resolves so partial completion cannot corrupt state.
Common Pitfalls
- Using boolean flags and polling loops instead of promise composition.
- Forgetting to return a promise inside helper functions, causing
undefinedflows. - Mixing callback style and
asyncstyle in the same chain without clear boundaries. - Using sequential awaits for independent operations and adding avoidable latency.
- Ignoring rejection handling, which can trigger unhandled promise rejections.
Summary
- Use
Promise.allto wait for two async operations before a dependent block. - Use
Promise.allSettledwhen partial success is acceptable. - Start independent tasks in parallel for lower latency.
- Keep orchestration logic explicit and test both success and failure paths.
- Avoid manual synchronization patterns that duplicate built in promise behavior.

