An async function returns a promise, and await pauses only that function while the awaited value settles—not the entire JavaScript program. Use Promise.all, Promise.allSettled, Promise.any, or Promise.race according to the result you need. For large batches, add a concurrency limiter so you do not start every operation at once.
What async/await does—and what it does not do
Calling an async function produces a promise. Within that function, await suspends execution until the awaited value settles, then resumes with its fulfillment value or throws its rejection. Other work that does not depend on that value can continue in the meantime. It does not freeze the whole program or create a separate thread. MDN’s guide to promises explains how awaiting in one async function leaves other asynchronous jobs able to run.
As an Amazon Associate I earn from qualifying purchases.
Sequential awaits start work in sequence
In this example, the second call is not made until the first one has finished:
const first = await fetchFirst();
const second = await fetchSecond();
Start independent work together
If both operations are independent and both results are needed, call them before awaiting the combined result:
#1 Best Overall
const [firstResult, secondResult] = await Promise.all([
fetchFirst(),
fetchSecond(),
]);
This starts both calls without waiting for the first to finish before starting the second. Keep sequential awaits when the second operation depends on the first result; that dependency is real and should remain explicit. Promise composition coordinates asynchronous work; it does not make CPU-heavy synchronous JavaScript execute in parallel. Actual parallel JavaScript execution requires a mechanism such as worker threads.
Which Promise combinator should you use?
Choose based on when you need the aggregate promise to settle and what failures mean for your application.
Rank #2
| Method | When the aggregate settles | Use it when | Failure and cancellation behavior |
|---|---|---|---|
Promise.all(iterable) |
Fulfills after every input fulfills; rejects as soon as an input rejects. | Every result is required, and any failure should fail the combined operation. | A rejection does not cancel other operations already started. Fulfillment values are returned in input order. |
Promise.allSettled(iterable) |
After every input settles. | You need a success-or-failure record for every task. | Fulfills with outcome objects marked fulfilled or rejected, in input order. It does not cancel tasks. |
Promise.any(iterable) |
When the first input fulfills, or after all inputs reject. | Any one successful result is enough. | Rejections are disregarded while a possible fulfillment remains; if all inputs reject, it rejects with an AggregateError. It does not cancel remaining operations. |
Promise.race(iterable) |
As soon as the first input fulfills or rejects. | The first outcome is decisive, for example, in a timeout race. | The first rejection wins just as the first fulfillment does. Settling the race does not cancel the other operations. |
These behaviors are described in MDN’s promise guide. In particular, an early aggregate result is not a signal to stop the operations that lost the race or were still running when another input rejected.
How to limit concurrency for a large batch
This common pattern starts every mapped operation immediately:
const results = await Promise.all(items.map(item => doWork(item)));
That can be appropriate for a small batch. For a large input, eagerly starting all the work may put too many simultaneous requests on a service or consume more runtime resources than intended. A limiter queues calls and allows only a chosen number to execute at a time.
Use p-limit to cap active work
The p-limit project documentation shows this pattern:
Rank #4
import pLimit from 'p-limit';
const limit = pLimit(5);
const results = await Promise.all(
items.map(item => limit(() => doWork(item)))
);
The value 5 is an example setting, not a generally correct limit. Pick a cap based on the external service’s limits, memory use, latency, and the behavior of the work. The limiter controls how many functions run concurrently; Promise.all still controls how the returned promises are aggregated. If one rejects, the aggregate rejects, but the limiter does not thereby cancel other active work.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Queue helpers and visibility
p-limit also documents limit.map(iterable, mapper) as a convenience and exposes active and pending counts. Its scope is focused concurrency limiting; the project describes p-queue as a fuller queue abstraction with more controls and options. Check the documentation for the version you have installed when relying on package-specific behavior.
Best Value
What happens to work after failure, a race, or shutdown?
Aggregate rejection does not stop other tasks
If Promise.all rejects, already-started operations continue unless their underlying APIs support cancellation and you explicitly cancel them. The same distinction matters when Promise.race or Promise.any settles early: the result of the aggregate promise does not terminate the operations that are still running.
Cancel a timed-out request where possible
For cancellable operations such as fetch, use AbortController to signal cancellation when a timeout wins. Without an abort mechanism supported by the underlying operation, it may continue consuming resources after the race has settled. See MDN’s discussion of promises and cancellation.
Clearing p-limit’s waiting queue
p-limit’s clearQueue() discards calls that have not started; it does not cancel active tasks. With its documented default, rejectOnClear: false, promises for discarded pending calls may remain unresolved if your code is awaiting them. Account for that behavior when designing shutdown or cancellation paths.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Avoid waiting on the same limiter from inside itself
Do not have a task occupying a slot in a limiter wait for nested work that must acquire another slot from that same limiter. The outer task can hold the slot the inner task needs, leaving both waiting. Use a separate limiter for nested work when it needs its own concurrency cap.
Quick Recap
A practical decision sequence
- Identify dependencies. Keep dependent operations in sequence; start independent operations together.
- Choose the outcome policy. Use
Promise.allwhen every success is required,Promise.allSettledto collect every outcome,Promise.anywhen any fulfillment is sufficient, orPromise.racewhen the first settlement decides the result. - Decide whether the batch needs a cap. If starting all items at once could overload a service or the runtime, wrap each operation in a limiter.
- Plan for operations that outlive the aggregate. Explicitly abort supported work when it should stop; do not assume a rejection, timeout race, or cleared queue cancels active operations.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




