October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk4 min

Understanding async/await, Promise Combinators, and Concurrency Limits in JavaScript

Understand when JavaScript async/await runs work sequentially or concurrently, how Promise combinators handle outcomes, and how a limiter caps a large batch.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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:

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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:

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

A practical decision sequence

  1. Identify dependencies. Keep dependent operations in sequence; start independent operations together.
  2. Choose the outcome policy. Use Promise.all when every success is required, Promise.allSettled to collect every outcome, Promise.any when any fulfillment is sufficient, or Promise.race when the first settlement decides the result.
  3. 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.
  4. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.