Catch an Asynchronous Failure

Catch an Asynchronous Failure

A try block catches an awaited rejection because await turns that rejection into a thrown error at the continuation. Without await, returning a rejected promise can leave the local catch bypassed.

Before this topic, review Fetch and Validate a Response if its terms are unfamiliar.

What you’ll learn

  • Explain catch an asynchronous failure through the school example.
  • Trace the shown operations and distinguish their result from displayed output.
  • Make the practice change and explain its consequence: The awaited rejection is still caught, but its reason now describes invalid data.

Work through the school example

This is JavaScript using language features and the host console. Read the initial values, then follow each operation to its displayed result. A declaration names a value, a function call performs an operation, and console.log displays its arguments.

JavaScript · school.js · complete example

async function report() {
  try {
    await Promise.reject(new Error("Missing mark"));
  } catch (error) {
    console.log(error.message);
  }
}
report();

Expected output

Missing mark
Run in JavaScript compiler →

Understanding Error Handling in Asynchronous JavaScript

When writing asynchronous code in JavaScript, such as using Promises or async/await, it's important to handle errors properly. Errors in async code behave differently compared to synchronous code, so we must be cautious and deliberate in our handling strategy.

Why Error Handling is Important in Async Code?

  • Network requests can fail due to connectivity or server issues.
  • Third-party APIs can return errors or unexpected data formats.
  • File operations or database queries may timeout or reject.

Example 1: Error Handling in Promises using .catch()

Let’s say we have a promise that may reject due to some error. We can handle it using the .catch() method.

function getData() {
  return new Promise((resolve, reject) => {
    const success = false; // Change this to true to test success
    if (success) {
      resolve("Data fetched successfully!");
    } else {
      reject("Failed to fetch data.");
    }
  });
}

getData()
  .then(result => console.log(result))
  .catch(error => console.error("Error:", error));
Error: Failed to fetch data.

How does .catch() work?

The .catch() method is attached to the end of a promise chain. It catches any error that is thrown or passed to reject() in the chain.

Question:

What happens if we forget to add .catch()?

The error will be unhandled, and in modern environments, it might log a warning or throw a UnhandledPromiseRejectionWarning.

Example 2: Error Handling with async/await and try-catch

With async/await, we can write asynchronous code in a synchronous style. To catch errors, we wrap the code in a try-catch block.

async function fetchUserData() {
  try {
    const user = await getData(); // using the same getData function from above
    console.log(user);
  } catch (err) {
    console.error("Caught inside async/await:", err);
  }
}

fetchUserData();
Caught inside async/await: Failed to fetch data.

Why use try-catch with async/await?

Because await pauses execution until the promise resolves or rejects. If it rejects, control jumps to the catch block.

Question:

Can I mix async/await with .catch()?

Yes, although it is not common. Usually, you'd prefer try-catch with await. But technically, you can do this:

async function fetchData() {
  await getData()
    .then(data => console.log(data))
    .catch(err => console.error("Mixed handling:", err));
}

fetchData();

Example 3: Global Promise Rejection Handler

Sometimes you want to catch unhandled promise rejections globally, especially in larger apps.

process.on("unhandledRejection", (reason, promise) => {
  console.log("Unhandled Rejection at:", promise, "reason:", reason);
});

This is useful in Node.js environments for logging or cleanup.

Best Practices

  • Always handle errors in your promises and async functions.
  • Use try-catch with await for more readable code.
  • For multiple async operations, handle each individually or use Promise.allSettled().
  • Log errors with enough context to debug later.

Conclusion

Asynchronous JavaScript is powerful, but only if we handle errors properly. Whether you use promises or async/await, error handling ensures your application behaves predictably and avoids silent failures.

Recognize the important boundary

A catch should recover, report or rethrow intentionally. Silently replacing every failure with success hides defects.

Try a small variation

JavaScript · original source for practice

async function report() {
  try {
    await Promise.reject(new Error("Missing mark"));
  } catch (error) {
    console.log(error.message);
  }
}
report();

Your task: Replace Missing mark with Invalid mark. Predict the result before running the edited program.

The source is repeated here. If the edit targets the HTML rather than the script, use the markup repeated below; the worked answer also repeats the changed markup.

Worked answer and reasoning

The awaited rejection is still caught, but its reason now describes invalid data.

JavaScript · complete worked variation

async function report() {
  try {
    await Promise.reject(new Error("Invalid mark"));
  } catch (error) {
    console.log(error.message);
  }
}
report();

Expected output

Invalid mark
Run in JavaScript compiler →

Predict the original program

JavaScript · original program for this question

async function report() {
  try {
    await Promise.reject(new Error("Missing mark"));
  } catch (error) {
    console.log(error.message);
  }
}
report();
What does the original JavaScript program display, in order?
Choose one answer. You can try again.

Answer and explanation

Missing mark

A try block catches an awaited rejection because await turns that rejection into a thrown error at the continuation. Without await, returning a rejected promise can leave the local catch bypassed. A catch should recover, report or rethrow intentionally. Silently replacing every failure with success hides defects.

Keep the useful idea

A try block catches an awaited rejection because await turns that rejection into a thrown error at the continuation. Without await, returning a rejected promise can leave the local catch bypassed.

Check your variation against this explanation: The awaited rejection is still caught, but its reason now describes invalid data.

Use the JavaScript array reference library for individual array operations and boundary tests to check a rule independently.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: Trace Tasks and Promise Handlers →