Result Type Async Error Handling
Replace thrown exceptions with a Result union for expected failures, chaining async operations without try-catch pyramids or lost errors.
Code
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
const ok = <T>(value: T): Result<T, never> => ({ ok: true, value });
const err = <E>(error: E): Result<never, E> => ({ ok: false, error });
class ValidationError {
constructor(readonly field: string, readonly reason: string) {}
}
async function fetchUser(id: string): Promise<Result<{ id: string; email: string }, Error>> {
if (!/^[a-z0-9-]+$/.test(id)) {
return err(new ValidationError('id', 'invalid characters'));
}
const res = await fetch('https://api.example.com/users/' + id);
if (!res.ok) {
return err(new Error('upstream status ' + res.status));
}
return ok(await res.json());
}
async function mapResult<T, U, E>(
result: Result<T, E>, fn: (value: T) => Promise<U>,
): Promise<Result<U, E>> {
if (!result.ok) return result;
return ok(await fn(result.value));
}
const user = await fetchUser('u-123');
const decorated = await mapResult(user, async (u) => ({ ...u, email: u.email.toUpperCase() }));
if (decorated.ok) {
console.log('email:', decorated.value.email);
} else if (decorated.error instanceof ValidationError) {
console.log('field problem:', decorated.error.field);
} else {
console.log('failure:', decorated.error.message);
}Report a problem with this exampleOpens a pre-filled issue on GitHub.