Error Handling Tipado
El manejo de errores con try/catch tiene una limitación en TypeScript: el parámetro catch siempre es unknown (o any), por lo que TypeScript no puede verificar qué tipo de error puede lanzar una función. No hay forma de saber leyendo el tipo de una función si puede fallar y de qué maneras.
El patrón Result<T, E> resuelve esto: hace explícito en el tipo de retorno que la función puede tener éxito (con datos) o fallar (con un error), y el caller debe manejar ambos casos.
Las clases de error personalizadas con campos tipados (código de error, campos de validación, etc.) permiten hacer narrowing en el catch: if (e instanceof NotFoundError) sabes que tiene e.code y e.message específicos de ese tipo de error.
En TypeScript moderno con "useUnknownInCatchVariables": true en tsconfig, el parámetro catch es unknown por defecto, obligándote a verificar el tipo antes de acceder a sus propiedades, lo que evita crashes por asumir que el error siempre tiene ciertos campos.
// Patrón Result — sin excepciones
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
// Uso en lugar de try/catch
async function buscarUsuario(id: string): Promise<Result<Usuario>> {
const usuario = await db.usuario.findUnique({ where: { id } });
if (!usuario) {
return { success: false, error: new Error(`Usuario ${id} no encontrado`) };
}
return { success: true, data: usuario };
}
// El caller DEBE manejar ambos casos
const resultado = await buscarUsuario("123");
if (resultado.success) {
console.log(resultado.data.nombre); // TypeScript sabe: data es Usuario
} else {
console.error(resultado.error.message); // TypeScript sabe: error es Error
}
// Errores personalizados tipados
class NotFoundError extends Error {
readonly code = "NOT_FOUND" as const;
constructor(recurso: string, id: string) {
super(`${recurso} con id "${id}" no encontrado`);
this.name = "NotFoundError";
}
}
class ValidationError extends Error {
readonly code = "VALIDATION_ERROR" as const;
constructor(readonly campos: Record<string, string>) {
super("Error de validación");
this.name = "ValidationError";
}
}
// catch tipado con unknown
try {
await operacion();
} catch (e: unknown) {
if (e instanceof NotFoundError) {
res.status(404).json({ code: e.code, message: e.message });
} else if (e instanceof ValidationError) {
res.status(400).json({ code: e.code, campos: e.campos });
} else {
res.status(500).json({ message: "Error interno" });
}
}
Practica esta lección
Resuelve los ejercicios del curso de TypeScript en la app, gratis y con corrección automática.
Empezar a practicar