Back to JavaScript
JavaScript
medium
mid

How do you handle asynchronous code using async/await and Promises?

Default to async/await for readability. Use Promise.all for parallel independent work, Promise.allSettled when you want every result regardless of failure, Promise.race / any for first-to-finish. Always wrap awaits in try/catch (or .catch on the call site). Pass an AbortSignal for cancellation. Never await sequentially when work is independent.

4 min read·~10 min to think through

Async/await for sequential reading

js
async function load(userId) {
  const user = await fetchUser(userId);
  const posts = await fetchPosts(user.id);
  return { user, posts };
}

Reads like sync; debugger stops at each line. Use this as the default.

Parallel independent work

js
const [user, prefs] = await Promise.all([fetchUser(id), fetchPrefs(id)]);

Two unrelated calls — fire both at once, await both. Don't write await fetchUser; await fetchPrefs — that's sequential and wastes a round trip.

When a parallel call may fail

js
const results = await Promise.allSettled([fetchA(), fetchB(), fetchC()]);
for (const r of results) {
  if (r.status === "fulfilled") use(r.value);
  else log(r.reason);
}

Promise.all rejects on the first failure; allSettled always resolves with per-promise outcomes.

Race / first-to-finish

js
const winner = await Promise.race([fetch(url), timeout(5000)]);

Promise.any resolves on the first fulfilled (rejects only if all reject) — handy for "try multiple mirrors, take the first to succeed."

Error handling

js
async function load() {
  try {
    const data = await fetchData();
    return data;
  } catch (err) {
    if (err.name === "AbortError") return null;
    log(err);
    throw err;   // or wrap in domain error
  }
}

Wrap awaits in try/catch — or let the rejection propagate and catch at a higher boundary (component error boundary, request handler).

Cancellation

js
const ac = new AbortController();
fetch(url, { signal: ac.signal }).then(...).catch((e) => {
  if (e.name === "AbortError") return;
  throw e;
});
ac.abort();

For React: pass signal in useEffect; abort on cleanup.

Sequential when needed

If step B depends on step A's value, sequential is correct:

js
const user = await fetchUser();
const posts = await fetchPosts(user.id);   // depends on user

If they're independent, don't sequence them.

Streaming results

For early progress, don't await all — fire and render as each resolves:

js
fetchA().then(setA);
fetchB().then(setB);

Or with React Suspense:

jsx
<Suspense fallback={<Skeleton />}>
  <UserData id={id} />
</Suspense>

Common mistakes

  • Sequential awaits for independent work.
  • Forgetting to await a Promise (returns Promise instead of value).
  • map(async ...) and not Promise.all-ing the result.
  • Throwing inside async expecting sync caller to catch (rejection is async).
  • Unhandled rejections crashing Node.

Interview framing

"Async/await for readability by default. Promise.all for independent work that should run in parallel — biggest perf bug I see is sequential awaits. Promise.allSettled when failures are acceptable. Promise.race / Promise.any for timeouts and first-successful patterns. Wrap awaits in try/catch or let them propagate to an error boundary. Pass AbortSignal for cancellation — critical in React effects and aborting stale requests. The two patterns that catch people: map(async ...) without Promise.all, and forgetting to await before logging the result."

Follow-up questions

  • Compare Promise.all and Promise.allSettled.
  • When have you used Promise.race?
  • How does AbortController integrate with fetch?

Common mistakes

  • Sequential awaits for independent work.
  • Missing await.
  • map(async) without Promise.all.
  • No try/catch around awaits with side effects.

Performance considerations

  • Parallelizing independent work saves N-1 RTTs. Cancellation prevents wasted compute on stale data.

Edge cases

  • Promise.all([]) resolves to [] immediately.
  • Returning a Promise from an async function — it gets flattened.
  • Top-level await behavior in modules.

Real-world examples

  • React Query, server-side data loaders, batch ETL, parallel API aggregation.

Senior engineer discussion

Seniors identify parallel opportunities and use Promise.all/allSettled deliberately, treat cancellation as a first-class concern, and surface errors at proper boundaries rather than swallowing.

Related questions