Errores personalizados en JavaScript: clases, códigos y cause | Nicolás Garzón
Un error personalizado representa un fallo con significado propio dentro de una aplicación o dominio.
JavaScript
Copiar class ProductNotFoundError extends Error {
constructor ( productId, options = { } ) {
super (
` Product ${ productId} was not found ` ,
options,
) ;
this . name = "ProductNotFoundError" ;
this . code = "PRODUCT_NOT_FOUND" ;
this . productId = productId;
}
} JavaScript
Copiar class ValidationError extends Error {
constructor ( message, options = { } ) {
super ( message, options) ;
this . name = "ValidationError" ;
}
} JavaScript
Copiar throw new ValidationError (
"Email is invalid" ,
) ; JavaScript
Copiar error instanceof ValidationError ;
error instanceof Error ; Ambas expresiones son true dentro del mismo realm.
Una subclase no obtiene automáticamente un name específico en todas las formas esperadas.
JavaScript
Copiar class ValidationError extends Error { }
new ValidationError ( "Invalid" ) . name;
Establecerlo mejora el diagnóstico:
JavaScript
Copiar this . name = "ValidationError" ; No lo uses como único identificador estable; también es una propiedad modificable.
JavaScript
Copiar class InsufficientStockError extends Error {
constructor ( {
productId,
requested,
available,
cause,
} ) {
super (
"There is not enough stock" ,
{ cause } ,
) ;
this . name = "InsufficientStockError" ;
this . code = "INSUFFICIENT_STOCK" ;
this . productId = productId;
this . requested = requested;
this . available = available;
}
} El mensaje ayuda al humano. Las propiedades permiten decisiones y registros precisos.
JavaScript
Copiar if ( error instanceof InsufficientStockError ) {
showStockMessage ( {
requested : error. requested,
available : error. available,
} ) ;
} JavaScript
Copiar class RepositoryUnavailableError extends Error {
constructor ( message, options = { } ) {
super ( message, options) ;
this . name = "RepositoryUnavailableError" ;
this . code = "REPOSITORY_UNAVAILABLE" ;
}
} JavaScript
Copiar try {
await database. query ( sql) ;
} catch ( cause) {
throw new RepositoryUnavailableError (
"Products cannot be loaded" ,
{ cause } ,
) ;
} La capa traduce un detalle de infraestructura y conserva el origen.
JavaScript
Copiar throw new Error (
` Cannot load products: ${ cause. message} ` ,
) ; Esto pierde el objeto original, su tipo, stack y propiedades.
JavaScript
Copiar throw new Error (
"Cannot load products" ,
{ cause } ,
) ; El sistema de logging puede recorrer la cadena cuando sea necesario.
JavaScript
Copiar this . code = "PRODUCT_NOT_FOUND" ; Un código es útil cuando:
El error cruza procesos o APIs.
El consumidor no comparte la misma clase.
Puede existir otro realm.
Necesitas métricas por categoría.
La interfaz traduce mensajes.
No generes decisiones comparando frases humanas.
JavaScript
Copiar class DomainError extends Error { }
class OrderError extends DomainError { }
class InvalidOrderStateError extends OrderError { } Una jerarquía puede permitir manejo por nivel:
JavaScript
Copiar if ( error instanceof DomainError ) {
} Pero demasiadas clases generan complejidad sin beneficio.
Crea una clase cuando aporta al menos una de estas cosas:
Manejo distinto.
Datos propios.
Traducción entre capas.
Métricas o observabilidad.
Un contrato público.
No crees una clase nueva solo para cambiar una frase.
JavaScript
Copiar class ValidationError extends Error {
constructor ( issues, options = { } ) {
super (
"The input contains invalid fields" ,
options,
) ;
this . name = "ValidationError" ;
this . code = "VALIDATION_FAILED" ;
this . issues = issues;
}
} JavaScript
Copiar throw new ValidationError ( [
{
field : "email" ,
code : "INVALID_EMAIL" ,
} ,
{
field : "age" ,
code : "OUT_OF_RANGE" ,
} ,
] ) ; El mensaje general permanece estable y cada issue describe un problema concreto.
Cuando varios errores independientes deben conservarse:
JavaScript
Copiar const errors = [ ] ;
for ( const notifier of notifiers) {
try {
await notifier. send ( message) ;
} catch ( error) {
errors. push ( error) ;
}
}
if ( errors. length > 0 ) {
throw new AggregateError (
errors,
"Some notifications failed" ,
) ;
} No lo uses cuando solo existe una causa lineal; para eso cause expresa mejor la cadena.
Texto
Copiar cause → un error provocó otro
AggregateError → varios errores forman un resultado conjuntoJavaScript
Copiar JSON . stringify (
new Error ( "Failed" ) ,
) ;
Muchas propiedades estándar de Error no son enumerables.
En una frontera API, crea una representación explícita.
JavaScript
Copiar function toPublicError ( error ) {
return {
code : error. code ?? "INTERNAL_ERROR" ,
message : publicMessageFor ( error) ,
} ;
} No serialices automáticamente stack, cause o propiedades internas hacia clientes.
JavaScript
Copiar class ValidationError extends Error {
constructor ( issues ) {
super ( "Validation failed" ) ;
this . name = "ValidationError" ;
this . code = "VALIDATION_FAILED" ;
this . issues = issues;
}
toJSON ( ) {
return {
name : this . name,
code : this . code,
message : this . message,
issues : this . issues,
} ;
}
} Puede ser útil en un contrato controlado. Aun así, decide qué datos pueden salir de la aplicación; no expongas todo por comodidad.
Un error creado dentro de un iframe puede pertenecer a otro constructor global.
JavaScript
Copiar error instanceof Error ; Puede no comportarse como esperas al cruzar realms.
Para contratos entre ventanas, workers, servicios o JSON, utiliza datos explícitos:
JavaScript
Copiar {
code : "PRODUCT_NOT_FOUND" ,
message : "Product not found"
} Las instancias son útiles dentro del mismo proceso; los datos son más portables entre fronteras.
El stack se relaciona con el momento de creación del Error.
JavaScript
Copiar const error = new Error ( "Failed" ) ; Crear el error cerca del problema suele dar una traza más útil que retornar un código y construir un Error muchas capas después.
JavaScript
Copiar new RepositoryUnavailableError (
"Product query failed after connection timeout" ,
) ; Texto
Copiar No pudimos cargar los productos. Inténtalo nuevamente.No tienen que ser iguales. El mensaje técnico ayuda a diagnosticar; la interfaz protege detalles y orienta al usuario.
JavaScript
Copiar class InvalidOrderStateError extends Error {
constructor ( {
orderId,
currentStatus,
expectedStatus,
cause,
} ) {
super (
` Order ${ orderId} must be ${ expectedStatus} ` ,
{ cause } ,
) ;
this . name = "InvalidOrderStateError" ;
this . code = "INVALID_ORDER_STATE" ;
this . orderId = orderId;
this . currentStatus = currentStatus;
this . expectedStatus = expectedStatus;
}
} JavaScript
Copiar if ( order. status !== "pending" ) {
throw new InvalidOrderStateError ( {
orderId : order. id,
currentStatus : order. status,
expectedStatus : "pending" ,
} ) ;
} El error conserva información suficiente para UI, logs y pruebas sin depender del texto.
Extender Error sin establecer un nombre claro.
Crear una clase distinta para cada mensaje.
Usar el mensaje como identificador del programa.
Perder la causa original.
Exponer stack y detalles internos al cliente.
Crear jerarquías profundas sin manejo diferente.
Usar AggregateError para una sola cadena causal.
Confiar únicamente en instanceof al cruzar procesos o realms.
Guardar contraseñas, tokens o información sensible en propiedades del error.
Una clase personalizada expresa un fallo propio del sistema.
Debe extender Error y conservar su comportamiento.
name, code y datos estructurados cumplen funciones diferentes.
cause conserva una cadena de causalidad.
AggregateError representa múltiples errores independientes.
Las instancias funcionan mejor dentro del mismo proceso.
En fronteras, utiliza representaciones públicas explícitas.
No necesitas una clase para cada frase.
¿Cuál es la diferencia entre estas dos relaciones?
JavaScript
Copiar new Error ( "Cannot load config" , { cause } ) ;
new AggregateError ( errors, "Several tasks failed" ) ; Respuesta cause representa una cadena donde un fallo originó otro. AggregateError reúne varios fallos que forman parte del mismo resultado conjunto.
Validación de datos explica cómo impedir que información externa inválida entre al núcleo de la aplicación.