Result Pattern: Manejo Elegante de Errores sin Excepciones
design-patternsbehavioralresult-pattern

Intención
El Result Pattern representa el resultado de una operación que puede fallar, encapsulando tanto el éxito como el error en un tipo unificado. Elimina la necesidad de lanzar excepciones para el flujo de control.
El Problema
Las excepciones tienen problemas:
// ¿Qué excepciones puede lanzar esta función?
function processPayment(amount: number): PaymentResult {
// ¿ValidationError? ¿NetworkError? ¿InsufficientFundsError?
// La firma no lo dice...
}
try {
const result = processPayment(100);
// ¿Es seguro usar result aquí?
} catch (e) {
// ¿Qué tipo de error es e? unknown...
}La Solución: Result Type
classDiagram
class Result~T, E~ {
<<interface>>
+isOk(): boolean
+isErr(): boolean
+unwrap(): T
+unwrapOr(default: T): T
+map~U~(fn: T => U): Result~U, E~
+andThen~U~(fn: T => Result~U, E~): Result~U, E~
+match~R~(handlers): R
}
class Ok~T~ {
-value: T
+isOk(): true
+unwrap(): T
}
class Err~E~ {
-error: E
+isErr(): true
+unwrapErr(): E
}
Result <|-- Ok
Result <|-- ErrImplementación TypeScript
type Result<T, E> = Ok<T, E> | Err<T, E>;
class Ok<T, E = never> {
readonly _tag = 'Ok' as const;
constructor(readonly value: T) {}
isOk(): this is Ok<T, E> { return true; }
isErr(): this is Err<T, E> { return false; }
unwrap(): T { return this.value; }
unwrapOr(_default: T): T { return this.value; }
map<U>(fn: (value: T) => U): Result<U, E> {
return ok(fn(this.value));
}
andThen<U>(fn: (value: T) => Result<U, E>): Result<U, E> {
return fn(this.value);
}
match<R>(handlers: { ok: (v: T) => R; err: (e: E) => R }): R {
return handlers.ok(this.value);
}
}
class Err<T, E> {
readonly _tag = 'Err' as const;
constructor(readonly error: E) {}
isOk(): this is Ok<T, E> { return false; }
isErr(): this is Err<T, E> { return true; }
unwrap(): T { throw new Error('Called unwrap on Err'); }
unwrapOr(defaultValue: T): T { return defaultValue; }
map<U>(_fn: (value: T) => U): Result<U, E> {
return err(this.error);
}
andThen<U>(_fn: (value: T) => Result<U, E>): Result<U, E> {
return err(this.error);
}
match<R>(handlers: { ok: (v: T) => R; err: (e: E) => R }): R {
return handlers.err(this.error);
}
}
// Factory functions
const ok = <T, E = never>(value: T): Result<T, E> => new Ok(value);
const err = <T = never, E = Error>(error: E): Result<T, E> => new Err(error);
// Utility para convertir funciones que lanzan excepciones
function tryCatch<T, E = Error>(
fn: () => T,
onError: (e: unknown) => E = (e) => e as E
): Result<T, E> {
try {
return ok(fn());
} catch (e) {
return err(onError(e));
}
}Ejemplo Práctico
// Tipos de error del dominio
type ValidationError = { type: 'validation'; field: string; message: string };
type NotFoundError = { type: 'not_found'; resource: string };
type UserError = ValidationError | NotFoundError;
// Función que retorna Result
function validateEmail(email: string): Result<string, ValidationError> {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return err({
type: 'validation',
field: 'email',
message: 'Invalid email format',
});
}
return ok(email.toLowerCase().trim());
}
// Composición de operaciones
const result = validateEmail(input)
.andThen(email => checkEmailNotExists(email))
.map(email => ({ email, verified: false }));
// Pattern matching
const message = result.match({
ok: user => `User created: ${user.email}`,
err: error => `Error in ${error.field}: ${error.message}`,
});Cuándo Usar
| Situación | Recomendación |
|---|---|
| Validaciones y parsing | ✅ Recomendado |
| APIs con errores tipados | ✅ Recomendado |
| Composición de operaciones | ✅ Recomendado |
| Errores verdaderamente excepcionales | ❌ Usar excepciones |
Conclusión
El Result Pattern hace explícitos los caminos de error, facilita la composición y elimina los try/catch dispersos.