Skip to content

Sequential vs Parallel Async Operations

When you have multiple async operations, the order matters. Sometimes each operation depends on the previous result — they must run sequentially. Sometimes they are independent — they can run in parallel, finishing much faster.

await one after another runs them in series:

async function loadAll() {
const rates = await fetchRates(); // waits ~500ms
const expenses = await fetchExpenses(); // waits another ~300ms
return { rates, expenses };
// total: ~800ms
}

The second await does not start until the first one resolves. This is correct when the second call depends on the first result — but wasteful when both are independent.

Promise.all accepts an array of Promises and returns a single Promise that resolves when all of them resolve:

async function loadAll() {
const [rates, user] = await Promise.all([
fetchRates(), // starts immediately
fetchUserData(), // starts immediately, in parallel
]);
return { rates, user };
// total: max(~500ms, ~300ms) = ~500ms
}

Both requests start at the same time. The combined wait is the duration of the slowest request, not the sum of all requests.

The result is an array of resolved values in the same order as the input array — destructure it directly.

If any Promise in the array rejects, Promise.all immediately rejects with that error:

try {
const [rates, extras] = await Promise.all([
fetchRates(),
fetchOptionalData(), // if this rejects...
]);
} catch (error) {
// ...this catch runs immediately, even if fetchRates was still pending
}

This is fine when all the operations are equally required. When some are optional, handle them separately.

Promise.allSettled — when partial success is acceptable

Section titled “Promise.allSettled — when partial success is acceptable”

Promise.allSettled waits for all Promises regardless of whether they fulfill or reject. It returns an array of result objects:

const results = await Promise.allSettled([
fetchRates(),
fetchUserPreferences(),
]);
for (const result of results) {
if (result.status === 'fulfilled') {
console.log('Got:', result.value);
} else {
console.warn('Failed:', result.reason.message);
}
}

Use Promise.allSettled when you want partial results — load what you can, degrade gracefully for what fails.

On startup, BudgetBuddy needs the exchange rates and any user preferences (display currency, categories). Both are independent:

async function init() {
const [ratesResult, prefsResult] = await Promise.allSettled([
fetchRates(),
loadUserPreferences(),
]);
const rates = ratesResult.status === 'fulfilled' ? ratesResult.value : { USD: 1 };
const prefs = prefsResult.status === 'fulfilled' ? prefsResult.value : defaultPrefs;
renderExpenses(loadExpenses(), rates, prefs);
}

Neither failure crashes initialization. The app uses safe defaults and renders.

SequentialParallel (Promise.all)
Each result is needed to start the nextOperations are independent
One request builds on the previousFastest possible combined response
Operations must happen in a specific orderPartial failure should crash everything
 Use allSettled when partial success is OK
  1. Write two stub functions that return Promises with artificial delays (setTimeout-based). Time how long running them sequentially takes vs in parallel with Promise.all.
  2. Demonstrate that Promise.all rejects immediately if one Promise rejects — even if the other is still pending.
  3. Use Promise.allSettled with one failing and one succeeding Promise — extract the successful value while handling the failure.
  4. In BudgetBuddy’s init(), use Promise.allSettled to load rates and user preferences in parallel with graceful fallbacks for each.
  • Sequential await runs operations one after another — correct when each depends on the previous result.
  • Promise.all([...]) runs all Promises in parallel — resolves when all succeed, rejects fast if any fail.
  • Promise.allSettled([...]) runs all in parallel — resolves when all settle, regardless of success or failure.
  • Use allSettled when partial results are acceptable; use all when all results are required.
  • Always time your async code — sequential vs parallel can mean the difference between a 2-second load and a 0.5-second load.