All tutorials
    TypeScript

    Mastering Async/Await Error Handling

    Write resilient asynchronous code with clean, predictable error handling patterns.

    6 min readJun 2, 2026

    The Problem with Unhandled Promises

    When an awaited promise rejects, the error propagates up the call stack. Without a try/catch, you get an unhandled rejection that can crash your process or silently swallow failures.

    A Clean try/catch Pattern

    Wrap your awaited calls in a try/catch and rethrow after logging so callers can still react to the failure.

    TypeScript
    async function fetchData() {
      try {
        const response = await fetch('/api/data');
        if (!response.ok) throw new Error('Bad response');
        return await response.json();
      } catch (error) {
        console.error('Failed:', error);
        throw error;
      }
    }

    Going Further with Result Types

    For predictable control flow, return a tuple of [error, data] instead of throwing. This forces callers to handle the error path explicitly.

    TypeScript
    async function safe<T>(p: Promise<T>): Promise<[Error | null, T | null]> {
      try {
        return [null, await p];
      } catch (e) {
        return [e as Error, null];
      }
    }