ReviseAlgo Logo

Asynchronous JavaScript

Error Handling with async/await

Master error handling patterns in JavaScript async/await. Learn to handle errors using try/catch blocks, propagate rejections, and write custom error wrapper utilities.

Last Updated: July 15, 2026 10 min read

1. Introduction

In async/await functions, errors (like network request rejections or parsing issues) are thrown as standard exceptions. Managing these exceptions requires using structured patterns like try/catch blocks or custom error wrapper utilities.

2. Why It Matters

Uncaught promise rejections can cause silent failures or crash Node.js servers in production. Using structured error handling ensures that errors are caught, failures are handled gracefully, and helpful diagnostic logs are written.

3. Real-World Analogy

Think of a Circuit Breaker Panel:

  • Uncaught Error (Power Surge): A short circuit in one outlet that causes a fire because there are no safety breakers installed.
  • try/catch Block (Circuit Breaker): A fuse box that monitors the electrical circuit. If a power surge occurs (an error is thrown), the breaker trips (control is transferred to the catch block) to cut power safely, preventing damage and keeping the rest of the house running.

4. Async Error Handling Patterns

Let's compare the three main patterns for handling errors in asynchronous functions:

1. Standard try/catch Block:

The standard way to handle exceptions. It wraps the asynchronous operations in a block, catching any errors that occur.

2. Promise catch Helper:

Since async functions return Promises, you can append a .catch() handler to the function call directly.

3. Go-style wrapper utility (await-to-js):

A pattern that wraps a promise to return a tuple: [error, data]. This keeps code flat by avoiding nested try/catch blocks.

5. Practical Example

This script demonstrates executing a multi-step checkout workflow with granular error handling using custom error types:

6. Common Mistakes

  • Swallowing errors: Catching an error but logging nothing or returning no default fallback values. This hides bugs and makes troubleshooting difficult.
  • Throwing plain strings: Always throw a true Error object (e.g. throw new Error('msg')) rather than a plain string, to preserve stack traces.

7. Quick Quiz

Q1: What happens if an awaited promise rejects inside an async function that does not have a try/catch block?

A) The function returns undefined

B) The function throws an exception, resulting in an unhandled promise rejection

Answer: B — The function throws an exception. If the caller does not catch this exception, it results in an unhandled promise rejection.

8. Scenario-Based Challenge

The Multi-Step Fallback Pipeline:

An analytics loader fetches metrics from regional nodes: Primary -> Secondary -> Local Cache. If Primary fails, check Secondary. If Secondary fails, load Local Cache. Write a robust fallback sequence using nested or sequential try/catch blocks.

9. Debugging Exercise

Explain why this error handler fails to catch the fetch rejection:

async function getMetrics() {
  try {
    // Bug: returning a promise without awaiting it exits the try block
    return fetch('/metrics.json').then(res => res.json()); 
  } catch (err) {
    console.log('Caught: ' + err.message); // never executes!
  }
}
getMetrics();
View Solution

Diagnosis: The function returns the Promise without awaiting it first. This exits the getMetrics function and its try/catch block immediately. When the Promise rejects later, the error is thrown outside the catch block.

Fix: Use the await keyword to wait for the Promise to resolve or reject inside the try block before returning:

async function getMetrics() {
  try {
    return await fetch('/metrics.json').then(res => res.json()); // Await before returning
  } catch (err) {
    console.log('Caught: ' + err.message); // Works!
  }
}

10. Interview Questions

🟢 Q1: Explain why returning a promise without awaiting it inside a try/catch block is a problem.

Answer: If you return a Promise without await, you return a reference to the Promise and exit the function's execution context immediately, leaving the try/catch block. When the Promise resolves or rejects later, the catch block no longer exists to handle the error, resulting in an unhandled promise rejection. Always use return await promise if you need to catch errors inside the current function.

11. Production Considerations

  • Custom Error Types: Define custom error classes (extending the built-in Error class) to represent different failure scenarios (e.g. NetworkError, ValidationError). This makes it easier to filter and handle errors selectively in your catch blocks.