unrust

Reliability guide

How do you review async/await and Promise error handling safely?

Published

Published by Unrust. Scoring and editorial method

Quick answer

To review async/await and Promise error handling safely, trace what happens when the second of three asynchronous operations rejects: verify that every Promise is either awaited, returned, or explicitly attached to an error handler, check whether Promise.all short-circuits and orphans in-flight sibling side effects, and confirm that cleanup in finally blocks does not mask or overwrite the original rejection.

Trace partial failure across concurrent Promises

When code runs await Promise.all([chargeCard(), writeLedger(), sendReceipt()]), a rejection in writeLedger() rejects the outer Promise.all immediately—while chargeCard() and sendReceipt() continue running to completion in the background.

During code review, always check whether the tasks inside Promise.all perform non-idempotent side effects that need sequenced ordering or explicit compensation when a sibling task fails.

Spot floating promises and try/catch return traps

Inside a try { return fetchRemote(id); } catch (err) { ... } block, returning a Promise without return await means a rejection from fetchRemote(id) bypasses the local catch block entirely and rejects at the caller instead.

Similarly, calling an async function inside .forEach(async (item) => ...) discards the returned Promises so the outer function resolves before any async iteration finishes.

Verify loop concurrency vs sequential ordering

An await inside a for...of loop executes sequentially, preserving order and rate limits, whereas mapping to an array of Promises starts every request concurrently before Promise.all awaits them.

Check whether the downstream API or database lock expects sequential execution or bounded concurrency before approving a refactor between for...of and Promise.all.

An async/await and Promise review table

Use this table when reviewing asynchronous control flow in JavaScript, TypeScript, or Python asyncio.

An async/await and Promise review table
Observed conditionReview decisionWhat to verify
A try/catch block returns the result of an async call directly (return doWork())Check whether the function uses return await doWork() inside try/catchVerify that a rejection from doWork() is caught by the local catch block rather than escaping unhandled to the caller
An array of items is processed with .forEach(async (item) => ...)Replace .forEach with for...of (sequential) or Promise.all(items.map(...)) (concurrent)Confirm the caller actually awaits completion and surfaces rejections from every item
Multiple side-effecting operations run together in Promise.all([...])Trace what happens to sibling operations if one Promise rejects mid-flightVerify whether steps must run sequentially or use Promise.allSettled with explicit partial-failure reconciliation

Interactive reasoning check

Try the async review: does the catch block run?

Consider this TypeScript function: async function loadProfile(id: string) { try { return fetchUserFromApi(id); } catch (err) { return { id, fallback: true }; } } If fetchUserFromApi(id) returns a Promise that rejects with a network timeout, what does await loadProfile('u1') do?
Read the reasoning without answering

In an async function, return promise; resolves/rejects the outer function with that Promise after exiting the try block. Only return await promise; suspends execution inside the try block so a rejection is caught locally.

A small practice loop

  1. 01Check every try/catch in an async function to confirm that returned Promises use return await.
  2. 02Search the diff for .forEach(async or unawaited async helper calls that could create floating Promises.
  3. 03Trace what happens when one branch of a Promise.all call rejects while sibling calls are still in flight.

Common questions

Does eslint's no-return-await rule mean I should remove await inside try/catch?

No—inside a try/catch block, return await is required so that a rejection is caught by the catch clause (or before a finally clause runs). Modern TypeScript-ESLint rules specifically require return await inside try/catch.

Where can I practice tracing interleaved async execution?

Try the free Async Log Timeline Explorer at /tools/async-log-timeline-explorer or take the Unrust diagnostic in JavaScript or TypeScript.

Limits of this guide

This guide focuses on control-flow and error-handling semantics in async functions; it does not cover OS-level socket tuning or framework-specific request cancellation.

Sources and scope

Original editorial guide by the Unrust team referencing ECMAScript Promise semantics and production incident patterns.

Check your own starting point

Unrust uses short, timed, unassisted drills to give developers a per-skill snapshot. One session cannot explain the cause of a result, but it can show you what is worth practicing next.

Take the free diagnostic