Skip to content
JavaScript Basics: Combine Tasks with Promise.all and Promise.allSettled

JavaScript Basics: Combine Tasks with Promise.all and Promise.allSettled

October 17, 2026

A promise represents an eventual result. When several independent tasks belong together, you can collect their promises and wait for the group. This tutorial compares an all-success result with a report that includes failures.

All examples use built-in features and were tested on Linux with Node.js v24.19.0. Strict assertions check the results and stop execution on a mismatch. No network access or package installation is needed.

Prepare a scratch directory

You need Bash, a text editor, the mktemp, rm, and rmdir utilities, and Node.js on your PATH. Keep using the same terminal throughout. Save each named file as UTF-8. The .mjs extension selects ES modules, which allow the top-level await used here.

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

1. Keep results in input order

Save this as collect-task-results.mjs:

import assert from 'node:assert/strict';

let finishFirstTask;
const firstTask = new Promise((resolve) => {
  finishFirstTask = resolve;
});
const secondTask = Promise.resolve('second result');

// The second promise is already fulfilled while the first is pending.
const combined = Promise.all([firstTask, secondTask, 'plain value']);
finishFirstTask('first result');
const results = await combined;

assert.deepEqual(results, ['first result', 'second result', 'plain value']);
assert.deepEqual(await Promise.all([]), []);
console.log(results.join(' | '));
console.log(`Empty input: ${JSON.stringify(await Promise.all([]))}`);

Run it:

node collect-task-results.mjs

Output:

first result | second result | plain value
Empty input: []

Promise.all() fulfills with values in input order, regardless of completion order. Ordinary values are accepted too. An empty input produces an empty array.

The executor passed to new Promise() runs when that promise is created. Promise.all() collects results; it does not call task functions for you. Pass the promises returned by calling those functions, rather than the functions themselves. For independent asynchronous tasks, create their promises before awaiting the group. This does not make CPU-heavy JavaScript run on multiple threads.

2. Observe rejection without cancelling other work

Save this as observe-group-rejection.mjs:

import assert from 'node:assert/strict';

let finishRemainingTask;
let remainingTaskFinished = false;
const remainingTask = new Promise((resolve) => {
  finishRemainingTask = () => {
    remainingTaskFinished = true;
    resolve('remaining result');
  };
});

const failedTask = Promise.reject(new Error('Report unavailable'));
try {
  await Promise.all([failedTask, remainingTask]);
  assert.fail('Expected the group to reject');
} catch (error) {
  assert.equal(error.message, 'Report unavailable');
  console.log(`Group failed: ${error.message}`);
}

assert.equal(remainingTaskFinished, false);
finishRemainingTask();
assert.equal(await remainingTask, 'remaining result');
assert.equal(remainingTaskFinished, true);
console.log(`Remaining task finished: ${remainingTaskFinished}`);

Run it:

node observe-group-rejection.mjs

Output:

Group failed: Report unavailable
Remaining task finished: true

The group rejects when an input rejects, without waiting for every task to finish. This does not cancel other operations. Here, the remaining promise can still fulfill after the group fails. Handle the group rejection with try/catch around await; cancellation, if supported by the underlying operation, needs a separate mechanism.

3. Collect successes and failures together

Save this as report-settled-tasks.mjs:

import assert from 'node:assert/strict';

const results = await Promise.allSettled([
  Promise.resolve('Profile ready'),
  Promise.reject(new Error('Photo missing')),
  Promise.resolve('Settings ready'),
]);

assert.deepEqual(results.map((result) => result.status), [
  'fulfilled', 'rejected', 'fulfilled',
]);
assert.equal(results[0].value, 'Profile ready');
assert.equal(results[1].reason.message, 'Photo missing');
assert.equal(results[2].value, 'Settings ready');
assert.deepEqual(await Promise.allSettled([]), []);

for (const [index, result] of results.entries()) {
  if (result.status === 'fulfilled') {
    console.log(`Task ${index + 1}: ${result.value}`);
  } else {
    console.log(`Task ${index + 1} failed: ${result.reason.message}`);
  }
}

Run it:

node report-settled-tasks.mjs

Output:

Task 1: Profile ready
Task 2 failed: Photo missing
Task 3: Settings ready

Promise.allSettled() waits until every input has either fulfilled or rejected. Its result records remain in input order. Check status before reading value or reason: fulfilled records have value, while rejected records have reason.

Use this form when a partial result is useful, such as a batch report. A failed input becomes a rejected record rather than rejecting the group. Inspect those records so failures do not disappear silently. Rejection reasons can be any JavaScript value; this example deliberately uses Error objects, so reading .message is appropriate here.

Neither method adds a timeout or limits how many tasks run at once. In particular, allSettled() keeps waiting if an input never settles. For large batches, choose a separate concurrency limit instead of starting every operation at once.

Clean up

In the same terminal, remove only these example files, return to your original directory, and remove the empty scratch directory:

rm -- collect-task-results.mjs observe-group-rejection.mjs report-settled-tasks.mjs
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

Acknowledgements

This post was created with assistance from AI.

Last updated on