Back to JavaScript Notes
Topic #369

Debugging Async Code

You will learn how to trace failures in JavaScript asynchronous code by using async stack traces, breakpoints, and error logging to see where a promise or await actually failed.

What it is

Debugging async code means finding the cause of a problem that happens after the current synchronous call stack has finished. JavaScript schedules callbacks, promise continuations, and await resumptions through the event loop, so a normal stack trace may stop at setTimeout, Promise, or an internal scheduler instead of showing the function that started the work.

The useful mental model is two timelines: the synchronous call stack and the asynchronous continuation queue. Related terms include event loop, microtasks, promise rejection, async stack traces, and source maps.

Why it matters

  • It reveals which async function called the failing step.
  • It helps locate unhandled promise rejections.
  • It makes race conditions and timing bugs easier to inspect.
  • It preserves useful error context when promises are chained.
  • It reduces guesswork when logs only show a generic error message.

Syntax or steps

The smallest useful pattern is to log the full error stack and enable async stack traces when available.

  1. Enable async stack traces in DevTools so the debugger can reconstruct logical async call chains.
  2. Put a breakpoint on the line containing await, or on the line after it, to inspect values before and after the promise settles.
  3. Wrap risky async calls in try/catch, or attach .catch() to a promise chain.
  4. Log err.stack, not only err.message, so the trace is preserved.

Example


function fetchProfile(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id < 0) {
        reject(new Error("profile id must be non-negative"));
      } else {
        resolve({ id, name: "Ada" });
      }
    }, 10);
  });
}

async function loadDashboard(id) {
  const profile = await fetchProfile(id);
  return { title: "Welcome", user: profile.name };
}

async function main() {
  try {
    const dashboard = await loadDashboard(-1);
    console.log(dashboard);
  } catch (err) {
    console.error("main failed:", err.stack);
  }
}

main();

fetchProfile returns a promise that settles later inside setTimeout. If id is negative, it rejects with an Error. loadDashboard uses await, so its execution pauses until the promise settles. main catches the rejection and logs err.stack. With async stack traces enabled, the stack can show the logical path through main and loadDashboard, even though the actual rejection happened in a timer callback.

Common mistakes

  • Logging only err.message. Fix: log err.stack or pass the error object to a logger that preserves stack traces.
  • Forgetting to await a promise. Fix: use await or attach .catch() so rejections are handled.
  • Setting a breakpoint only on the synchronous call. Fix: set it on the await line or on the rejection path.
  • Swallowing errors in a .then() chain. Fix: return the promise and add a final .catch() that logs the error.

When to use it

Use async stack traces when the failure is deep in a promise chain and the normal stack is truncated. Use breakpoints when you need to inspect variables at a known async boundary. Use try/catch or .catch() when the program must recover or report the failure.

TechniqueBest forUse when
Async stack tracesSeeing the full await/promise call chainAn error is thrown inside async code and the normal stack is incomplete
Breakpoints on await linesInspecting values before and after a promise settlesYou know the suspicious async step but not the exact failure
try/catch or .catchHandling or reporting failuresThe program must continue after a rejected promise

Practice

  1. Guided: Run the example, then change loadDashboard(-1) to loadDashboard(1). Expected output: an object like { title: "Welcome", user: "Ada" }.
  2. Challenge: Add another async function that calls loadDashboard and rejects with a different error. Hint: log err.stack in the outermost catch and enable async stack traces.

Quick check

Question: Why might a normal stack trace not show the function that called an async callback?

Answer: Because the callback runs later from the event loop after the original synchronous stack has unwound; async stack traces reconstruct the logical call chain.

Summary

Async debugging works by connecting the visible error to the asynchronous continuation that caused it. Enable async stack traces, inspect await boundaries, and log full error stacks. This turns truncated traces into a practical map of the async flow.

Want to go beyond the notes?

Join Coding Now Tech Institute's JavaScript course — live mentorship, real projects, and 100% placement support.

Enroll Now — Free Demo Available

Debugging Async Code – FAQs

Quick answers about learning Debugging Async Code in JavaScript.

This free note from Coding Now Tech Institute explains Debugging Async Code in JavaScript — concept, syntax and worked code examples you can copy, run and revise before interviews.
Yes. Every JavaScript topic on Coding Now Tech Institute, including Debugging Async Code, is 100% free with no signup required.
With focused practice, most students grasp Debugging Async Code in 1–3 days from these notes; pairing it with Coding Now Tech Institute's mentor-led course takes you to job-ready depth faster.
Use the code examples in this note, then ask doubts for free on the Coding Now Tech Institute Community (/community) — expert instructors answer within 24 hours.
Call NowEnroll Now