Manejar errores correctamente es una de las partes más importantes y menos cuidadas del código JavaScript. Un manejo deficiente hace que los errores pasen desapercibidos, que los mensajes no sean descriptivos o que el programa quede en un estado inconsistente. Conocer los tipos de error nativos, cómo crear errores propios y cómo capturarlos en código asíncrono es fundamental.
try/catch/finally
El bloque try contiene el código que puede fallar. catch captura el error. finally siempre se ejecuta, tanto si hubo error como si no:
function parsearJSON(texto) {
try {
const datos = JSON.parse(texto);
return datos;
} catch (err) {
// err es el objeto Error lanzado por JSON.parse
console.error('JSON inválido:', err.message);
return null;
} finally {
// Siempre se ejecuta: ideal para limpieza
console.log('Intento de parseo completado');
}
}
console.log(parsearJSON('{"a":1}')); // { a: 1 }
console.log(parsearJSON('no json')); // null
// Si el bloque finally devuelve un valor, anula el return del try:
function ejemploFinally() {
try {
return 'try';
} finally {
return 'finally'; // Este es el valor devuelto
}
}
console.log(ejemploFinally()); // 'finally'
Tipos de Error predefinidos
JavaScript incluye varios tipos de Error especializados. Identificarlos permite actuar de forma diferente según el tipo de fallo:
// TypeError: operación en un tipo incorrecto
try {
null.propiedad;
} catch (err) {
console.log(err instanceof TypeError); // true
console.log(err.name); // 'TypeError'
}
// ReferenceError: variable no declarada
try {
console.log(variableInexistente);
} catch (err) {
console.log(err instanceof ReferenceError); // true
}
// RangeError: valor fuera del rango permitido
try {
new Array(-1);
} catch (err) {
console.log(err instanceof RangeError); // true
}
// SyntaxError: generalmente en eval()
try {
eval('función rota {');
} catch (err) {
console.log(err instanceof SyntaxError); // true
}
// Identificar el tipo en catch:
try {
// código que puede lanzar varios tipos de error
} catch (err) {
if (err instanceof TypeError) {
// error de tipo
} else if (err instanceof RangeError) {
// error de rango
} else {
throw err; // Re-lanzar errores desconocidos
}
}
Crear errores personalizados extendiendo Error
Extender Error permite crear jerarquías de errores propios con información adicional:
class ErrorValidacion extends Error {
constructor(campo, mensaje) {
super(mensaje);
this.name = 'ErrorValidacion';
this.campo = campo;
}
}
class ErrorHTTP extends Error {
constructor(statusCode, mensaje) {
super(mensaje);
this.name = 'ErrorHTTP';
this.statusCode = statusCode;
}
esClientError() { return this.statusCode >= 400 && this.statusCode < 500; }
esServerError() { return this.statusCode >= 500; }
}
// Uso:
function validarEdad(edad) {
if (typeof edad !== 'number') {
throw new ErrorValidacion('edad', 'La edad debe ser un número');
}
if (edad < 0 || edad > 120) {
throw new ErrorValidacion('edad', `Edad fuera de rango: ${edad}`);
}
}
try {
validarEdad('treinta');
} catch (err) {
if (err instanceof ErrorValidacion) {
console.log(`Campo: ${err.campo}, Mensaje: ${err.message}`);
}
}
Manejo de errores en código asíncrono
// Con async/await: try/catch captura errores de promesas rechazadas
async function obtenerDatos(url) {
try {
const r = await fetch(url);
if (!r.ok) throw new ErrorHTTP(r.status, r.statusText);
return await r.json();
} catch (err) {
if (err instanceof ErrorHTTP && err.esClientError()) {
console.error('Error del cliente:', err.message);
return null;
}
throw err; // Re-lanzar errores de servidor o de red
}
}
// Con Promises: .catch() al final de la cadena
fetch('/api/datos')
.then(r => r.json())
.then(procesar)
.catch(err => {
console.error('Error en la cadena:', err);
});
Captura global de errores no manejados
// En el navegador: errores síncronos no capturados
window.onerror = function(mensaje, url, linea, columna, error) {
console.error('Error global:', { mensaje, url, linea, columna });
return true; // true evita que el mensaje aparezca en consola
};
// En el navegador: promesas rechazadas sin .catch()
window.addEventListener('unhandledrejection', (evento) => {
console.error('Promesa rechazada sin manejar:', evento.reason);
evento.preventDefault(); // Evita que aparezca en consola
});
// En Node.js:
process.on('uncaughtException', (err) => {
console.error('Error no capturado:', err);
process.exit(1);
});
process.on('unhandledRejection', (reason) => {
console.error('Promesa rechazada sin manejar:', reason);
});
El anti-patrón del catch vacío
Nunca uses un catch vacío o que solo registre el error y siga adelante como si nada. Es uno de los errores más peligrosos porque oculta fallos:
// MAL: oculta el error completamente
try {
operacionPeligrosa();
} catch (e) {} // El error desaparece
// MAL: registra pero ignora el estado inconsistente
try {
operacionPeligrosa();
} catch (e) {
console.log(e); // Y sigue adelante como si todo fuera bien
}
// BIEN: actúa según el tipo de error o re-lanza
try {
operacionPeligrosa();
} catch (e) {
if (esRecuperable(e)) {
recuperar();
} else {
throw e; // Re-lanza para que el llamador lo maneje
}
}
La propiedad err.stack contiene la traza de llamadas en el momento del error y es fundamental para depurar. Asegúrate de registrarla en producción con tu sistema de logging o error tracking.
