Desk Clarity

A field guide to everyday digital problems.

Developer tools / Practical guide

Awaiting an Array Map Does Not Await Its Tasks: Aggregate the Returned Promises

An async map callback returns a promise for each element.

By ON Lab2 min readSources checked November 10, 2026 (KST)

An async map callback returns a promise for each element. Awaiting the array itself leaves those promises inside the array. Use Promise.all when you need one ordered result after every input task fulfills.

Reproduce in an empty temporary directory

This isolated example creates only fixture data in its working directory or runs local JavaScript. Run it separately from your project so the named files cannot overwrite real work.

const assert = require('node:assert/strict');
(async () => {
  const jobs = [1, 2, 3].map(async value => value * 2);
  const unchanged = await jobs;
  assert.equal(unchanged, jobs);
  assert(unchanged.every(value => value instanceof Promise));
  const results = await Promise.all(jobs);
  assert.deepEqual(results, [2, 4, 6]);
  console.log(JSON.stringify(results));
})().catch(error => { console.error(error); process.exitCode = 1; });

Observed local output

[2,4,6]

What to compare

The first await receives an ordinary array value and returns that array. Promise.all instead observes the input promises and returns their fulfillment values in input order. This order is independent of completion timing. The task calls already happened during map; adding Promise.all afterward does not itself limit how many operations started.

Choose the next action

Use Promise.all for a batch that should fail if any required task fails. Use allSettled when every outcome must be inspected, and process its rejected entries rather than dropping them. For rate-limited or resource-heavy operations, start work through a bounded worker queue instead of mapping the whole input immediately. For an intentionally sequential dependency, use a for...of loop with an await at the actual operation.

Limits and verification

An aggregate rejection does not cancel sibling work that is already running. Own cleanup or cancellation through the API each task supports, and preserve evidence of partial side effects before retrying. The fixture contains only local arithmetic and promises; it does not contact a service or demonstrate a concurrency bound. This differs from the related forEach guide: here the map produces an array that you can aggregate explicitly.

Related: Awaiting ForEach Finishes Too Early: Own the Callback Promises Explicitly.

Isolated example executed on macOS on November 10, 2026 (KST), using Bash 3.2, Python 3 for the byte fixture, and Node v26.5.0. Results describe the example, not your application or every runtime version.

Sources & further reading