FSM Full Stack Masterclass
Platform under construction
JavaScript course badge

Errors & Debugging

Advanced

Async Debugging

Async debugging is about following work that completes later: promises, timers, fetch calls and event-driven code.

async function loadData() {
  try {
    const response = await fetch("/api/records");
    return await response.json();
  } catch (error) {
    console.error(error);
  }
}

Errors & Debugging

Async errors need clear await, catch and status paths.

Async code does not fail on the same timeline as synchronous code. A promise rejection happens later, so it needs await with try/catch or a .catch handler.

Fetch adds another trap: HTTP error statuses do not reject by themselves. You must check response.ok.

Async debugging often means tracking where a promise was created, where it was awaited and where its rejection was handled.

Promise rejection

An async failure that must be handled.

await try/catch

The cleanest way to catch errors in async functions.

response.ok

HTTP status check for fetch responses.

Unhandled rejection

A promise failed without a handler.

Examples

Good debugging code keeps the failure path visible.

Async function with status and parse handling

async function loadJson(url) {
  const response = await fetch(url);

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }

  return response.json();
}

Async error without await or catch

function run() {
  loadData();
}

// If loadData rejects, this function does not handle it.

Code patterns

Reusable examples for quick reference.

These examples focus on the debugging patterns you will reuse: safe parsing, throwing, custom error types, console inspection, breakpoints and async failure handling.

try/catch with await

Catch async failures in one readable block.

async function run() {
  try {
    const data = await loadData();
    return data;
  } catch (error) {
    return null;
  }
}

Promise catch

Attach a handler when not using await.

loadData().catch((error) => {
  console.error(error);
});

Fetch status check

Throw on bad HTTP status deliberately.

const response = await fetch("/api/records");
if (!response.ok) {
  throw new Error(`HTTP ${response.status}`);
}

Finally for loading state

Clear loading state after success or failure.

loading = true;
try { await loadData(); }
finally { loading = false; }

Rules that matter

Make failures observable and recoverable where possible.

Debugging starts before something breaks. Clear error types, helpful messages and visible failure states make the code easier to repair.

Await promises you depend on

Otherwise errors can escape the visible flow.

Use try/catch around await

It reads like synchronous error handling.

Check response.ok

Fetch does not reject for HTTP error statuses.

Use finally for loading state

Cleanup should happen on success and failure.

Keep async chains readable

Deep .then chains are harder to debug.

Report unhandled failures

Unexpected async errors should reach monitoring or logs.

Production thinking

Reliable software is built by handling failure deliberately.

Why does this matter?

Async failures are easy to miss because they happen later. Clear handling keeps later work visible.

Accessibility

Loading and error states should be announced or shown clearly so users are not left waiting silently.

Production note

Production async code needs timeout, abort, status handling and rejection reporting.

SEO note

Content that appears only after async work may not be reliable for SEO unless rendered or hydrated carefully.

Live code lab

Change the HTML, CSS or JavaScript and run the result.

The preview runs inside an isolated iframe. The JavaScript is placed inside the HTML editor for now, so every example stays together and remains easy to understand.

Mini assignment

Try this now.

  • Change the async task to resolve successfully.
  • Add a finally block that writes a console message.
  • Remove await and explain why catch no longer works the same way.

Practice assignment

Do this before moving to the next topic.

  1. Write one async function.
  2. Catch an awaited rejection.
  3. Check response.ok in a fetch example.

Try it yourself

Catch an async failure

Live preview

Self-check

Before you continue, prove that you understand Async Debugging.

Advanced

A JavaScript feature is not production-ready until you know how it fails and how you will inspect that failure.

  1. Can you explain promise rejection?
  2. Can you explain try/catch around await?
  3. Can you explain response.ok?
  4. Can you explain finally for loading state?
  5. Can you explain unhandled rejections?

Chapter checkpoint

Errors & Debugging checkpoint

Finish this chapter by turning the lessons into a small practical proof.

Build

Build a small example that combines three lessons from this chapter.

Deliverables

  • working code
  • short explanation
  • self-check answers

Quality checks

  • readable code
  • clear failure path
  • accessibility considered

Review question

Can you explain the important tradeoff without reading from the page?