Practical debugging, step by step

A missing await is a control-flow bug

When an async function is called without await, the next line receives a Promise—not the eventual value.

DoesItHold Editorial · · reviewed · guide-js-await-001

Direct answer

Add await at the boundary where the resolved value is required, and ensure the containing function is async.

An async function always returns a Promise. Without await, property access, arithmetic or branching happens against that Promise object instead of its resolved value. The failure may be immediate, such as reading an undefined property, or delayed until a value reaches a different layer.

Treat the defect as a sequencing problem. The useful question is not ‘where can I add await?’ but ‘which operation first requires the settled value?’ Keeping that boundary narrow preserves concurrency for independent work and makes the dependency visible.

Trace the failure
  1. 01
    CallfetchUser() returns Promise<User>
  2. 02
    Boundaryname is needed synchronously
  3. 03
    Suspendawait yields this continuation
  4. 04
    Resumeuser is now the resolved User
await does not block the thread. It suspends this async continuation until the Promise settles, then resumes with the resolved value.

Minimal example

Scrollable code region

Buggy
async function loadName() {
  const user = fetchUser();
  return user.name;
}
Corrected
async function loadName() {
  const user = await fetchUser();
  return user.name;
}

What changes: The corrected version reads name only after user is a resolved value; the buggy version reads it from Promise<User>.

Debugging method

Inspect the runtime value, follow the Promise to its first synchronous consumer and place await at the narrowest boundary that needs the result.

  1. Log or inspect the value and confirm whether it is a Promise before changing code.
  2. Find the first property read, comparison or calculation that needs the resolved value.
  3. Make the containing function async, add await there and keep independent Promises concurrent.
  4. Exercise both the success and rejection paths so the fix does not swallow an error.

Common wrong fix

Adding a timeout, optional chaining or a second Promise wrapper hides the symptom; none restores the required sequencing. Adding await to every expression can also serialize work that was independent.

Limits and edge cases

Top-level await is restricted by the script or module environment, and awaiting inside loops can unintentionally serialize requests. When operations are independent, start them together and await a combinator such as Promise.all at the point their results are needed.

Sources