Introducción: Por qué el manejo de errores es la base de un código robusto
Los errores en el código son inevitables. Surgen debido a datos incorrectos del usuario, fallos de red, ausencia de archivos en el servidor o simples errores tipográficos del desarrollador. En el entorno Node.js, donde el código se ejecuta de forma asíncrona y a menudo interactúa con recursos externos (bases de datos, sistema de archivos, API), un error no manejado puede provocar la caída de todo el proceso. Un manejo adecuado de errores en JavaScript transforma una aplicación frágil en un servicio resistente. En este artículo, analizaremos todos los mecanismos clave: desde el clásico try/catch hasta la creación de clases de error propias y el trabajo con código asíncrono.
1. Fundamentos: try, catch, finally y throw
El bloque básico para el manejo de excepciones en JavaScript es la construcción try...catch...finally. Permite "atrapar" un error que ocurrió en una sección "peligrosa" del código y reaccionar adecuadamente sin interrumpir la ejecución de todo el programa.
Sintaxis y funcionamiento de try/catch
El código que podría lanzar una excepción se coloca dentro del bloque try. Si ocurre un error, el control se transfiere inmediatamente al bloque catch, donde está disponible el objeto de error. Si no hay error, el bloque catch se ignora.
try { const data = JSON.parse('{"name": "Ana"}'); // JSON correcto console.log(data.name); // Imprime: Ana} catch (error) { console.error('Error de análisis:', error.message);}// La ejecución continúaAhora simulemos un error:
try { const data = JSON.parse('json no válido'); // ¡Error! console.log(data.name);} catch (error) { console.error('Error de análisis:', error.message); // Salida: Error de análisis: Unexpected token 'j', ... is not valid JSON}console.log('¡El programa sigue vivo!'); // Este código se ejecutaráBloque finally: código que se ejecuta siempre
El bloque finally se ejecuta independientemente de si hubo un error o no. Es ideal para liberar recursos (cerrar archivos, conexiones de base de datos) o limpiar temporizadores.
let resource = null;try { resource = acquireResource(); // Obtenemos el recurso resource.doSomething(); // Operación peligrosa} catch (error) { console.error('Error:', error);} finally { if (resource) { resource.close(); // Siempre cerramos el recurso console.log('Recurso liberado.'); }}Operador throw: creando tus propios errores
Puedes "lanzar" un error tú mismo usando throw. Esto es útil para validar datos de entrada o interrumpir la ejecución cuando se viola la lógica de negocio. Como argumento se puede pasar cualquier cosa, pero la mejor práctica es usar un objeto Error o sus descendientes.
function divide(a, b) { if (typeof a !== 'number' || typeof b !== 'number') { throw new Error('Los argumentos deben ser números'); } if (b === 0) { throw new Error('La división por cero no está permitida'); } return a / b;}
try { console.log(divide(10, 0));} catch (error) { console.error('Error:', error.message); // Error: La división por cero no está permitida}2. Técnicas avanzadas: Clases de error propias y tipificación
En proyectos grandes, usar simplemente el Error estándar no es suficiente. Es necesario distinguir entre errores de red, validación, autorización y otros. Para ello, se crean clases personalizadas que extienden el Error incorporado.
Creación de errores personalizados
class ValidationError extends Error { constructor(message) { super(message); this.name = 'ValidationError'; // Nombre único para verificar }}
class NetworkError extends Error { constructor(message, statusCode) { super(message); this.name = 'NetworkError'; this.statusCode = statusCode; }}
function fetchUser(id) { if (!id) { throw new Valida