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];
}
}