Clean Markup

Resilient Parallel Requests with Promise.allSettled in JavaScript

Resilient Parallel Requests with Promise.allSettled in JavaScript

When loading a dashboard that aggregates data from several independent microservices—such as user profile details, recent billing invoices, and optional activity recommendations—Promise.all() is the classic tool for firing requests concurrently. However, Promise.all() uses fail-fast semantics: if the non-critical recommendations endpoint times out or returns a 500 error, the combined promise rejects immediately and discards the resolved profile and billing payloads.

Standardized in ES2020, Promise.allSettled() solves this problem by waiting until every input promise has either fulfilled or rejected. It always resolves to an array of outcome descriptor objects ({ status: "fulfilled", value } or { status: "rejected", reason }), allowing your UI to render all successful widgets while displaying localized retry prompts for failed ones.

Example

// Promise.all rejects immediately if ANY single request fails
const [profile, invoices, recommendations] = await Promise.all([
  fetchProfile(),
  fetchInvoices(),
  fetchRecommendations() // If this rejects, profile & invoices are lost!
]);

// Promise.allSettled waits for all and reports each individual outcome
const results = await Promise.allSettled([
  fetchProfile(),
  fetchInvoices(),
  fetchRecommendations()
]);

Input : Array of concurrent Promise instances where some resolve and others reject
Output : Array of settled status descriptors (fulfilled with value, or rejected with reason).

Implementation

function fetchWidgetData(endpoint, shouldFail) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      if (shouldFail) {
        reject(new Error("Service unavailable for " + endpoint));
      } else {
        resolve({ endpoint: endpoint, items: [1, 2, 3] });
      }
    }, 120);
  });
}

async function loadDashboardWidgets() {
  const requests = [
    fetchWidgetData("/api/profile", false),
    fetchWidgetData("/api/notifications", true),
    fetchWidgetData("/api/analytics", false)
  ];

  const outcomes = await Promise.allSettled(requests);

  const succeeded = [];
  const errors = [];

  outcomes.forEach(function (outcome) {
    if (outcome.status === "fulfilled") {
      succeeded.push(outcome.value);
    } else {
      errors.push(outcome.reason.message);
    }
  });

  return {
    succeeded: succeeded,
    errors: errors
  };
}

loadDashboardWidgets().then(function (dashboard) {
  console.log("Rendered widgets:", dashboard.succeeded.length); // 2
  console.log("Isolated warnings:", dashboard.errors);
  // [ "Service unavailable for /api/notifications" ]
});

Main Point: Use Promise.all() when concurrent tasks are mutually dependent and partial completion is useless, but reach for Promise.allSettled() whenever independent UI panels or batch jobs should succeed or fail in isolation.

Credits

Photo by Headway

← Home