Manejo de errores en JavaScript: try/catch y más
El manejo de errores es una de las tareas clave en el desarrollo con JavaScript, especialmente en el entorno Node.js, donde la estabilidad de una aplicación servidora afecta directamente la experiencia del usuario. Las excepciones no manejadas pueden provocar la caída de todo el proceso y la pérdida de datos. En este artículo analizaremos no solo el bloque clásico try/catch, sino también técnicas avanzadas: el trabajo con finally, la creación de tipos de error personalizados, el manejo de errores asíncronos y las mejores prácticas para Node.js.
1. Fundamentos: try, catch y finally
El bloque try/catch es el mecanismo fundamental para interceptar excepciones síncronas. Si ocurre un error en el bloque try, la ejecución se transfiere inmediatamente al catch. El bloque finally se ejecuta siempre, independientemente de si hubo un error o no. Esto es útil para liberar recursos (cerrar archivos, conexiones).
const fs = require('fs');
function readConfig(path) { try { const data = fs.readFileSync(path, 'utf8'); console.log('Configuración cargada'); return JSON.parse(data); } catch (err) { console.error('Error al leer la configuración:', err.message); return null; } finally { console.log('Intento de lectura finalizado'); }}
const config = readConfig('./config.json');console.log(config); // null en caso de errorImportante: catch solo intercepta los errores que ocurren en try. Los errores en catch o finally quedan sin manejar.
2. Tipos de error en JavaScript y Node.js
En JavaScript existen varios tipos de error incorporados, cada uno de los cuales hereda de Error:
- SyntaxError — error de sintaxis al analizar el código.
- ReferenceError — referencia a una variable inexistente.
- TypeError — tipo de dato no válido (por ejemplo, llamar a algo que no es una función).
- RangeError — valor fuera del rango permitido.
- URIError — error al trabajar con URI (encodeURI/decodeURI).
En Node.js se añaden AssertionError (del módulo assert) y errores del sistema (EACCES, ENOENT, etc.) del módulo fs, que contienen el código de error en la propiedad code.
try { require('fs').readFileSync('/nonexistent');} catch (err) { if (err.code === 'ENOENT') { console.log('Archivo no encontrado'); } else { console.log('Otro error:', err.message); }}3. Creación de errores personalizados
Para un control más preciso sobre la lógica de manejo, se recomienda crear clases de error propias que extiendan Error. Esto permite distinguir los errores de lógica de negocio de las fallas del sistema.
class ValidationError extends Error { constructor(message, field) { super(message); this.name = 'ValidationError'; this.field = field; }}
class DatabaseError extends Error { constructor(message, query) { super(message); this.name = 'DatabaseError'; this.query = query; }}
function saveUser(user) { if (!user.email) { throw new ValidationError('Email es obligatorio', 'email'); } // simulación de error de BD throw new DatabaseError('Tiempo de espera de conexión agotado', 'INSERT INTO users');}
try { saveUser({ name: 'Iván' });} catch (err) { if (err instanceof ValidationError) { console.log(`Campo ${err.field}: ${err.message}`); } else if (err instanceof DatabaseError) { console.error(`Error de BD (${err.query}): ${err.message}`); // reintento o registro } else { console.error('Error desconocido:', err); }}El uso de instanceof permite construir cadenas de manejadores sin recurrir a la verificación de códigos de cadena.