Manejo de errores en JavaScript
Dominar el manejo de errores en JavaScript es crucial para crear aplicaciones robustas. Aprende a usar try-catch para operaciones síncronas, y el método catch() en Promesas o try-catch con async/await para la asincronía. Entender cómo se manejan los errores, usar throw para personalizarlos con el objeto Error, y aplicar finally para la limpieza, te dará el control total.

Índice
1 ¿Qué significa el manejo de errores en JavaScript?
En pocas palabras, el Manejo de Errores en JavaScript se refiere al conjunto de técnicas y estrategias que utilizamos para detectar, responder y gestionar situaciones inesperadas o no deseadas que ocurren durante la ejecución de nuestro código.
Imagina tu código como un camino. Normalmente, todo fluye bien y el programa sigue su curso esperado. Pero a veces, pueden surgir obstáculos:
- Un archivo que no existe.
- Una conexión a la red que falla.
- Un usuario que introduce datos incorrectos.
- Intentar acceder a una propiedad de un objeto que es
undefined. - Un error tipográfico en el nombre de una variable.
Estos obstáculos son lo que llamamos errores o excepciones. El manejo de errores nos permite identificar estos obstáculos y, en lugar de que el programa se detenga abruptamente (lo que se conoce como «crashing» o «romperse»), nos da la oportunidad de:
- CAPTURARLOS: Identificar que algo inesperado sucedió.
- ENTENDERLOS: Saber qué tipo de error es y por qué ocurrió.
- ACTUAR: Tomar una decisión sobre cómo proceder (por ejemplo, intentar de nuevo, mostrar un mensaje al usuario, registrar el error, etc.).
- RECUPERARSE (si es posible): Si la situación lo permite, permitir que el programa continúe su ejecución de manera controlada.
1.1. ¿Beneficios del manejo de errores?
El manejo de errores no es solo una buena práctica; es absolutamente crucial para el desarrollo de software profesional y de calidad por varias razones fundamentales:
- Robustez y fiabilidad de la aplicación:
- Previene caídas (crashes): Sin un manejo de errores adecuado, cualquier problema inesperado puede hacer que tu aplicación se detenga por completo, lo que es inaceptable en un entorno de producción.
- Mantiene la estabilidad: Permite que tu aplicación siga funcionando incluso frente a condiciones adversas o datos inesperados.
- Mejora la experiencia del usuario (UX):
- Mensajes claros y útiles: En lugar de ver una pantalla en blanco o un mensaje de error críptico, el usuario puede recibir un mensaje amigable que le informe sobre lo sucedido y, quizás, cómo solucionarlo (ej. «No se pudo cargar su perfil, por favor intente de nuevo más tarde»).
- Evita la frustración: Una aplicación que se rompe constantemente frustra al usuario y lo hará abandonar tu servicio o producto.
- Facilita la depuración y el mantenimiento:
- Identificación rápida de problemas: Cuando un error es capturado y registrado correctamente, los desarrolladores pueden identificar rápidamente qué salió mal y dónde, reduciendo el tiempo de depuración.
- Visibilidad en producción: En entornos en vivo, es tu única forma de saber qué problemas están experimentando tus usuarios, ya que no puedes estar mirando la consola del navegador de cada uno.
- Auditoría y análisis: Los logs de errores bien estructurados te permiten analizar patrones, identificar problemas recurrentes y mejorar la calidad de tu código a largo plazo.
- Seguridad:
- Un manejo de errores deficiente puede exponer información sensible a atacantes (por ejemplo, rutas de archivos internas o detalles de la base de datos en mensajes de error no controlados).
- Un manejo adecuado te permite controlar qué información se muestra y qué se registra.
- Desarrollo profesional:
- Es una expectativa fundamental en cualquier rol de desarrollo de software. Un profesional sabe que el código no solo debe funcionar en el «camino feliz» (happy path) sino también en los escenarios de «camino triste» (sad path).
- Demuestra madurez en tu código y consideración por los usuarios y otros desarrolladores que puedan trabajar con él.
RESUMEN: El manejo de errores es la red de seguridad de tu aplicación. Te permite anticipar lo inesperado, reaccionar de forma controlada y, en última instancia, entregar un producto más fiable, amigable y mantenible. Sin él, estás navegando a ciegas en un mar de incertidumbre, y tu aplicación está a un solo error de colapsar.
2. Tipos de errores nativos en JavaScript
Recuerda, cada uno de estos errores hereda del objeto base Error y te ayuda a entender qué salió mal.
2.1. ReferenceError
Este error ocurre cuando intentas usar una variable o función que no existe o no ha sido definida en tu código. JavaScript no sabe a qué te refieres.
// Ejemplo: Intentamos usar 'x' pero nunca la declaramos.
try {
console.log(x);
} catch (error) {
console.error("ReferenceError:", error.message);
// Salida: ReferenceError: x is not defined
}Lenguaje del código: JavaScript (javascript)
Explicación: El motor de JavaScript buscó una variable llamada x pero no la encontró en ningún lado.
2.2. TypeError
Se lanza cuando una operación no puede realizarse porque el valor no es del tipo de dato esperado. Por ejemplo, si intentas usar un método de texto en un número.
// Ejemplo: Intentamos usar un método de cadena en un número.
let numero = 123;
try {
numero.toUpperCase(); // .toUpperCase() es para cadenas, no para números.
} catch (error) {
console.error("TypeError:", error.message);
// Salida: TypeError: numero.toUpperCase is not a function
}Lenguaje del código: JavaScript (javascript)
Explicación: Estás intentando llamar a una función (toUpperCase) en un tipo de dato (number) que no la tiene.
NOTA!!!! Si te animas y deseas ver el resultado de este ejemplo y de todos los que iremos proporcionando directamente desde tu PC, te invito a seguir estos pasos.
- Primero, revisa el punto 5 del siguiente artículo sobre como usar e instalar VSCode (este es el editor de código mas usado), donde encontrarás los pasos para crear la estructura necesaria para visualizar una página desde tu navegador predeterminado.
- Si entendiste correctamente este paso anterior, sabrás donde colocar el código
scriptque te acabamos de proporcionar (arriba). Ahora, puedes visualizar el resultado, para ello puedes hacer lo siguiente:- Una vez abierta y visualizada la página, abre la consola del navegador presionando la tecla F12.
- Ahora, dirígete a la pestaña que dice
Console, y desde aquí podrás ver el resultado del script que hayamos introducido anteriormente.
- No te preocupes, estos pasos son más simples de lo que parecen!!!! Entonces… cada vez que dejemos un código de ejemplo como éste, puedes seguir estos pasos y ver los resultados por ti mismo. ¡Ojalá te animes!!!!
2.3. SyntaxError
Este error significa que hay un error en cómo escribiste el código, como un paréntesis que falta o una palabra clave mal usada. El código es «gramaticalmente» incorrecto para JavaScript.
// Ejemplo: Falta un paréntesis de cierre.
try {
eval("console.log('Hola'"); // Aquí falta un ')'
} catch (error) {
console.error("SyntaxError:", error.message);
// Salida: SyntaxError: Unexpected end of input
}Lenguaje del código: JavaScript (javascript)
Explicación: JavaScript no pudo entender la estructura de lo que le pediste que ejecutara porque le faltaba un componente básico de la sintaxis.
2.4. RangeError
Ocurre cuando un valor numérico está fuera de los límites o rangos aceptables. Es común con tamaños de arrays o ciertos argumentos de funciones.
// Ejemplo: Crear un array con una longitud negativa.
try {
const miArray = new Array(-5); // La longitud de un array no puede ser negativa.
} catch (error) {
console.error("RangeError:", error.message);
// Salida: RangeError: Invalid array length
}Lenguaje del código: JavaScript (javascript)
Explicación: Estás intentando definir algo (la longitud del array) con un valor que está fuera del rango permitido.
2.5. URIError
Este error aparece cuando hay problemas al codificar o decodificar cadenas URI (como URLs), por ejemplo, si la secuencia de caracteres no es válida.
// Ejemplo: Intentar decodificar una secuencia URI incompleta o mal formada.
try {
decodeURIComponent('%'); // El '%' debe ir seguido de dos dígitos hexadecimales.
} catch (error) {
console.error("URIError:", error.message);
// Salida: URIError: URI malformed
}Lenguaje del código: JavaScript (javascript)
Explicación: La función decodeURIComponent esperaba una secuencia de caracteres con un formato específico para decodificar, y lo que se le proporcionó no lo cumplía.
2.6. EvalError
Este error es muy poco común y prácticamente obsoleto en JavaScript moderno. Originalmente, estaba relacionado con problemas en la función eval(), pero hoy en día otros tipos de errores (como SyntaxError o TypeError) son lanzados en su lugar.
// Ejemplo: Aunque presente, EvalError rara vez se ve en la práctica actual.
// Si algo falla dentro de eval, suele ser un SyntaxError o TypeError.
try {
eval("throw new EvalError('Mi error de eval');"); // Lanzamiento manual para demostrarlo.
} catch (error) {
console.error("Error capturado (puede ser EvalError u otro):", error.name, error.message);
// Salida: Error capturado (puede ser EvalError u otro): EvalError Mi error de eval
}Lenguaje del código: JavaScript (javascript)
Explicación: Este error existe por compatibilidad hacia atrás, pero en la mayoría de los casos, los problemas dentro de eval generarán errores más específicos.
3. ¿Cómo manejar errores en JS? Try – catch
Hasta aquí ya vimos que significa manejar errores y los tipos de errores, pero ¿Cómo los manipulamos?. Para ellos usamos la estructura try-catch.
3.1. ¿Para qué sirve try...catch?
El bloque try...catch permite capturar errores en tiempo de ejecución de forma controlada, sin que el programa se detenga.
Es la herramienta básica para el manejo de errores en JavaScript.
3.2. Sintaxis básica:
try {
// Código que puede fallar
} catch (error) {
// Código para manejar el error
}Lenguaje del código: JavaScript (javascript)
Explicación:
- El código dentro de
try {}se ejecuta normalmente. - Si ocurre un error, JavaScript salta directamente al bloque
catch {}. - Dentro de
catch, el objetoerrorrepresenta la excepción que fue lanzada.
3.3. Ejemplo práctico:
try {
let resultado = 10 / 0;
console.log("Resultado:", resultado); // Esto sí se ejecuta (matemáticamente es Infinity)
let nombre = undefined;
console.log(nombre.toUpperCase()); // ❌ Esto lanza un error
} catch (error) {
console.error("⚠️ Se capturó un error:");
console.log("Mensaje:", error.message);
console.log("Nombre:", error.name);
console.log("Stack:", error.stack);
}Lenguaje del código: JavaScript (javascript)
🔍 Resultado:
⚠️ Se capturó un error:
Mensaje: Cannot read properties of undefined (reading 'toUpperCase')
Nombre: TypeError
Stack: [muestra el rastro de funciones que llevaron al error]Lenguaje del código: JavaScript (javascript)
¿Qué sucede en este ejemplo?
🔧En el bloque de try:
let resultado = 10 / 0;- Esto no lanza error.
- En JavaScript, dividir por cero da
Infinity. - ✅ Se ejecuta sin problemas.
let nombre = undefined;- Definimos la variable JS
nombresin valor (esundefined).
- Definimos la variable JS
- Llamamos a la variable anterior con:
console.log(nombre.toUpperCase());- ❌ Error: estás tratando de llamar un método
.toUpperCase()sobreundefined. - Esto lanza un
TypeError.
- ❌ Error: estás tratando de llamar un método
- Como ocurre un error dentro del
try, salta alcatch.
🔧 En el bloque catch (error),Se ejecuta el código que:
- Muestra:
"⚠️ Se capturó un error:" - Muestra el mensaje del error →
"Cannot read properties of undefined (reading 'toUpperCase')" - Muestra el tipo de error →
"TypeError" - Muestra el stack trace → (dónde ocurrió el error en el código)
3.4. El objeto error dentro del catch:
Cuando un error ocurre dentro del bloque try, la ejecución se detiene ahí y pasa al bloque catch, que recibe automáticamente un parámetro (por convención llamado error).
Dentro del catch:
errores el objeto que contiene toda la información del error ocurrido.- Con ese objeto podés acceder a:
error.message: un mensaje descriptivo del error.error.name: el tipo de error (comoTypeError,ReferenceError).error.stack: el rastreo de la pila que muestra dónde y cómo ocurrió el error (útil para depurar).
Así, dentro de catch podés:
- Evitar que la aplicación se caiga.
- Mostrar mensajes claros o amigables para el usuario.
- Registrar el error en consola o en un sistema de logs.
- Tomar acciones específicas según el tipo de error.
3.5. ¿Qué pasa si el error ocurre fuera del try?
Si el error ocurre fuera del bloque try, no será capturado, y el programa puede detenerse si no hay otro manejo global de errores.
❌ Ejemplo:
let nombre = null;
// Esta línea lanza error, pero está fuera de try...catch
console.log(nombre.toUpperCase()); // ❌
try {
console.log("Este bloque nunca se ejecutará si el error está afuera");
} catch (error) {
console.log("Este catch no atrapa el error");
}Lenguaje del código: JavaScript (javascript)
🧨 Resultado:
Uncaught TypeError: Cannot read properties of null (reading 'toUpperCase')Lenguaje del código: JavaScript (javascript)
🔍 Explicación:
El error ocurre antes de entrar al bloque try (Intentar usar .toUpperCase() sobre null genera un TypeError, porque null no tiene propiedades ni métodos), por lo tanto no puede ser atrapado. JavaScript muestra el error en consola y puede detener el script.
✅ Forma Correcta
try {
let nombre = null;
console.log(nombre.toUpperCase()); // Error atrapado
} catch (error) {
console.log("Error capturado:", error.message);
}Lenguaje del código: JavaScript (javascript)
🔍 Explicación:
Ahora, como vemos toda la ejecución que puede generar error está dentro del bloque try. Pero esta vez, el error es capturado inmediatamente por el bloque catch.
3.6. ¿Siempre debemos establece el objeto error dentro del catch?:
No, no siempre es obligatorio establecer el objeto error dentro del catch. Desde ES10 (ECMAScript 2019), JavaScript permite usar el bloque catch sin tener que declarar un parámetro para el error.
✅Ejemplo válido sin usar error
try {
let usuario = undefined;
console.log(usuario.nombre); // 💥 Esto lanza un TypeError
} catch {
console.log("❌ Ocurrió un error, pero no necesito saber cuál fue exactamente.");
}Lenguaje del código: JavaScript (javascript)
- ✔️ Este bloque
catches perfectamente válido. - No usamos
catch (error), solocatch. - Esto está permitido desde ECMAScript 2019 (ES10) en adelante.
- En este ejemplo estamos usando
console.log()dentro delcatchpara mostrar un mensaje personalizado sin necesidad de acceder al objetoerror - Se llama «optional catch binding».
4. ¿Qué es finally y para qué sirve?
El bloque finally es una parte opcional del manejo de errores con try...catch.
🧠 Su propósito:
Especificar código que SIEMPRE debe ejecutarse, independientemente de SI se produjo un error o NO.
Se usa principalmente para limpiar, cerrar, liberar, notificar, o hacer tareas finales sin importar cómo terminó el bloque try.
4.1. Sintaxis Completa con finally
try {
// Código que puede fallar
} catch (error) {
// Código para manejar el error
} finally {
// Código que SIEMPRE se ejecuta
}Lenguaje del código: JavaScript (javascript)
¿Qué hace esta sintaxis?
try- Siempre que el código llega ahí. Se coloca el código que puede fallar (por ejemplo, acceder a una propiedad de
undefined, operaciones con APIs, lectura de archivos, etc.).
- Siempre que el código llega ahí. Se coloca el código que puede fallar (por ejemplo, acceder a una propiedad de
catch- Solo si hubo un error en el
try. Se utiliza para manejar el error que ocurrió en el bloquetry. Podés usarconsole.log(), mostrar un mensaje al usuario, intentar una solución alternativa, etc.
- Solo si hubo un error en el
finally- Siempre se ejecuta, haya o no error. Se utiliza para manejar el error que ocurrió en el bloque
try. Podés usarconsole.log(), mostrar un mensaje al usuario, intentar una solución alternativa, etc.
- Siempre se ejecuta, haya o no error. Se utiliza para manejar el error que ocurrió en el bloque
4.2. Ejemplo sin error:
try {
console.log("✅ Todo funciona correctamente");
} catch (error) {
console.log("❌ Ocurrió un error");
} finally {
console.log("🔁 Esto siempre se ejecuta");
}Lenguaje del código: JavaScript (javascript)
Salida:
✅ Todo funciona correctamente
🔁 Esto siempre se ejecuta
4.3. Ejemplo con error
try {
let numero = undefined;
console.log(numero.toFixed(2)); // ❌ Error
} catch (error) {
console.log("❌ Se capturó el error:", error.message);
} finally {
console.log("🔁 Finalizando ejecución (con o sin error)");
}Lenguaje del código: JavaScript (javascript)
Salida:
❌ Se capturó el error: Cannot read properties of undefined (reading 'toFixed')
🔁 Finalizando ejecución (con o sin error)Lenguaje del código: JavaScript (javascript)
4.4. Casos específicos donde conviene usar finally
- Cerrar una conexión a una base de datos
- Asegurarte de cerrar la conexión aunque haya fallado la consulta o no.
- Liberar recursos del sistema
- Como cerrar archivos, streams, sockets o liberar memoria.
- Detener una animación o spinner de carga
- Si estás esperando una respuesta y mostrás una animación,
finallyte permite ocultarla sin importar si hubo error o no.
- Si estás esperando una respuesta y mostrás una animación,
- Quitar mensajes de estado temporales
- Como “Cargando…” o “Guardando cambios…”, que deben desaparecer sí o sí.
- Restablecer el estado de una variable o aplicación
- Por ejemplo, dejar en
falseuna variableisLoadingque se activó antes deltry.
- Por ejemplo, dejar en
- Eliminar datos temporales o de prueba
- Archivos, registros o datos creados para una operación puntual, que deben ser eliminados siempre.
- Reiniciar un flujo o volver a un punto seguro del programa
- Para no dejar al sistema en un estado inconsistente después de un error.
- Cerrar sesión o limpiar tokens temporales
- Por ejemplo, si se usó un token temporal de autenticación que ya no debe usarse después de la operación.
- Deshabilitar funciones o botones mientras se ejecuta algo
- Y volver a habilitarlos luego, aunque el código haya fallado.
5. Usando throw: Lanzando tus propios errores
Hasta ahora, hemos hablado de cómo los errores nativos de JavaScript se lanzan por sí solos y cómo try...catch los intercepta. Pero, ¿Qué pasa cuando eres tú quien quiere indicar que algo ha salido mal en tu propia lógica de negocio? Aquí es donde entra en juego la palabra clave throw.La declaración throw te permite generar tus propias excepciones (errores) de forma explícita.
Cuando ejecutas throw, la ejecución normal del programa se detiene, y el control salta inmediatamente al bloque catch más cercano en la pila de llamadas. Si no hay un catch que lo intercepte, el programa se detendrá con un error no controlado (un «crash»).
5.1. Sintaxis básica de throw
throw expresion;Lenguaje del código: JavaScript (javascript)
Donde expresion puede ser cualquier valor de JavaScript. Puedes lanzar:
- Un string (cadena de texto)
- Un número
- Un objeto (literal o instancia)
- Y, lo más importante y recomendado, una instancia del objeto
Error(o de una de sus clases hijas).
5.2. ¿Por qué throw es tan importante?
- Validación de datos: Puedes usar
throwpara indicar que los argumentos pasados a una función no son válidos. - Condiciones de negocio fallidas: Cuando una regla de negocio no se cumple (ej. un usuario intenta acceder a algo sin permisos, o una operación requiere un saldo mínimo que no se tiene).
- Estado inválido: Si tu aplicación llega a un estado que nunca debería suceder.
- Flujo de control: Aunque no es su propósito principal,
throwpuede alterar el flujo normal de un programa hacia un manejador de errores centralizado.
5.3. Ejemplos prácticos de throw
Veamos los diferentes tipos de valores que puedes lanzar y por qué es mejor lanzar objetos Error.
5.3.1. Ejemplo 1: Lanzando un string (No recomendado)
Aunque es posible, lanzar un simple string no te da toda la información que necesitas para una depuración efectiva.
console.log("--- Inicio Ejemplo 1: Lanzando un String ---");
function dividirNumeros(a, b) {
try {
if (b === 0) {
throw "¡Error: No se puede dividir por cero!"; // Lanzamos un string
}
return a / b;
} catch (error) {
console.error("Error capturado (string):", error);
// Salida: Error capturado (string): ¡Error: No se puede dividir por cero!
console.error("Tipo de error (name):", typeof error); // Salida: string
// No hay stack trace ni otras propiedades de Error
}
}
dividirNumeros(10, 0);
console.log("--- Fin Ejemplo 1 ---");
Lenguaje del código: JavaScript (javascript)
Explicación:
- Cuando
bes0, lanzamos directamente el string. - El
catchlo intercepta. Podemos imprimir elerrordirectamente, y vemos el string. - Problema:
errores solo un string. No tiene propiedades comonameostack, que son cruciales para entender el contexto del error y depurar. Es como recibir una nota de «Problema» sin saber quién la escribió ni dónde ocurrió.
5.3.2.Ejemplo 2: Lanzando un objeto literal (Mejor, pero aún no ideal)
Puedes lanzar un objeto JavaScript con tus propias propiedades. Esto es un paso adelante porque puedes incluir más información.
console.log("\n--- Inicio Ejemplo 2: Lanzando un Objeto Literal ---");
function validarContrasena(contrasena) {
try {
if (contrasena.length < 8) {
throw {
codigo: "PASSWORD_TOO_SHORT",
mensaje: "La contraseña debe tener al menos 8 caracteres.",
detalles: {
minimo: 8,
actual: contrasena.length
}
};
}
console.log("Contraseña válida.");
} catch (error) {
console.error("Error capturado (objeto):", error);
// Salida: Error capturado (objeto): {codigo: 'PASSWORD_TOO_SHORT', mensaje: 'La contraseña debe tener al menos 8 caracteres.', detalles: {…}}
console.error("Código de error:", error.codigo); // Salida: PASSWORD_TOO_SHORT
console.error("Mensaje de error:", error.mensaje); // Salida: La contraseña debe tener al menos 8 caracteres.
// Todavía no hay stack trace por defecto
}
}
validarContrasena("abc");
console.log("--- Fin Ejemplo 2 ---");
Lenguaje del código: JavaScript (javascript)
Explicación:
- Lanzamos un objeto que contiene información estructurada sobre el error.
- El
catchlo recibe y podemos acceder a sus propiedades (error.codigo,error.mensaje). - Mejora: Puedes proporcionar datos más ricos sobre el error.
- Problema: Todavía no obtienes un
stack traceautomáticamente (a menos que lo generes manualmente), y no se beneficia de la herencia del objetoErrorbase. Esto puede complicar la integración con herramientas de monitoreo de errores que esperan instancias deError.
5.3.3. Ejemplo 3: Lanzando una instancia del objeto Error (¡Lo más recomendado!)
Esta es la forma profesional y estándar de lanzar errores. Las instancias de Error (o sus subtipos como TypeError, o tus propios errores personalizados que extiendan de Error) te dan automáticamente las propiedades name, message y, crucialmente, el stack trace.
console.log("\n--- Inicio Ejemplo 3: Lanzando una Instancia de Error ---");
function procesarPedido(cantidad) {
try {
if (cantidad <= 0) {
// Lanzamos una instancia de Error
throw new Error("La cantidad del pedido debe ser un número positivo.");
}
console.log(`Procesando pedido de ${cantidad} unidades.`);
return true;
} catch (error) {
console.error("Error capturado (instancia de Error):");
console.error("Nombre del error:", error.name); // Salida: Error
console.error("Mensaje del error:", error.message); // Salida: La cantidad del pedido debe ser un número positivo.
console.error("Stack trace:\n", error.stack); // ¡Aquí está el valioso stack trace!
}
}
procesarPedido(-5);
procesarPedido(10); // Esta llamada no lanzará error y completará el try
console.log("--- Fin Ejemplo 3 ---");
Lenguaje del código: JavaScript (javascript)
Explicación:
- Cuando
cantidades0o menos, creamos una nueva instancia deErrorcon un mensaje descriptivo y la lanzamos. - El
catchla recibe. Ahora,error.namees «Error»,error.messagees el mensaje que le dimos, yerror.stacknos proporciona el seguimiento completo de la pila de llamadas hasta el punto donde se lanzó el error. - Beneficio: Obtienes todas las ventajas de los objetos
Errornativos, lo que facilita la depuración y la integración con herramientas de reporte de errores.
6. Errores asíncronos: La gran excepción de try...catch
¡Hemos llegado a un punto crucial y a menudo fuente de confusión para muchos desarrolladores de JavaScript! Si bien try...catch es excelente para manejar errores síncronos, su comportamiento cambia drásticamente cuando hablamos de código asíncrono. Comprender esto es vital para evitar dolores de cabeza en tus aplicaciones.
¿QUÉ ES LA ASINCRONÍA?
La asincronía en JavaScript permite ejecutar tareas que pueden tardar (como acceder a una API o leer archivos) sin detener el flujo del programa. Mientras se espera el resultado, el resto del código sigue corriendo normalmente. Una vez que la tarea finaliza, el resultado se maneja con promesas, callbacks o async/await. Esto evita bloqueos y mejora la fluidez de las aplicaciones web. Es clave para trabajar con operaciones lentas sin afectar la experiencia del usuario.
⚠️Aquí es donde try...catch se encuentra con un problema:
- Un bloque
try...catchactúa de forma síncrona. Se ejecuta de principio a fin de una sola vez. - 👉Si un error ocurre después de que el bloque
try...catchhaya terminado su ejecución, estetry...catchno lo capturará.
Imagina un cazador esperando en un arbusto (try...catch). Si el animal (error) pasa mientras el cazador está en el arbusto, lo captura. Pero si el animal aparece después de que el cazador ya se fue del arbusto, no puede capturarlo. El código asíncrono libera al «cazador» antes de que aparezca el «animal».
6.1. Ejemplos de errores asíncronos NO capturados por try...catch
Vamos a ver cómo esto se manifiesta en código.
6.1.1. Ejemplo 1: setTimeout y Callbacks
setTimeout es el ejemplo clásico de código asíncrono. La función de callback que le pasas se ejecuta en un momento futuro, después de que el try...catch externo ya haya terminado.
console.log("--- Inicio Ejemplo 1: setTimeout ---");
try {
console.log("Dentro del bloque try (síncrono).");
setTimeout(() => {
// Este código se ejecuta DESPUÉS de que el try...catch haya terminado.
console.log("\nEjecutando callback de setTimeout (asíncrono).");
// Provocamos un ReferenceError aquí
console.log(variableInexistenteDentroDeTimeout); // ESTO LANZARÁ UN ERROR
console.log("Esto no se imprimirá.");
}, 100); // Se ejecuta después de 100 milisegundos
console.log("Fin del bloque try (síncrono), antes del setTimeout.");
} catch (error) {
// Este catch NO capturará el error de variableInexistenteDentroDeTimeout.
console.error("¡Este catch NO capturó el error! Mensaje:", error.message);
}
console.log("Código después del try...catch (síncrono), continuará ejecutándose.");
/*
Salida esperada (orden aproximado):
--- Inicio Ejemplo 1: setTimeout ---
Dentro del bloque try (síncrono).
Fin del bloque try (síncrono), antes del setTimeout.
Código después del try...catch (síncrono), continuará ejecutándose.
Ejecutando callback de setTimeout (asíncrono).
// Aquí el programa CRASHEA con un "Uncaught ReferenceError" en la consola del navegador/Node.js
// o se reporta como un "Unhandled promise rejection" si es un async/await sin catch.
// El catch del bloque try...catch principal NO se activará.
*/
Lenguaje del código: JavaScript (javascript)
Explicación:
- El
try...catchprincipal se ejecuta.console.log("Dentro del bloque try...")yconsole.log("Fin del bloque try...")se imprimen. setTimeoutprograma su función de callback para que se ejecute en 100ms. La función de callback no se ejecuta inmediatamente.- El
try...catchprincipal finaliza su ejecución sin que se haya lanzado ningún error dentro de su propio tiempo de ejecución. Por lo tanto, elcatchexterno no tiene nada que atrapar. - Después de 100ms, el callback del
setTimeoutse ejecuta. Es en este momento cuandoconsole.log(variableInexistenteDentroDeTimeout)lanza unReferenceError. - Como no hay un
try...catchdentro de la propia función del callback para capturarlo, el error se convierte en un error no capturado (unhandled error) y detendrá tu aplicación (en Node.js) o se registrará como un error en la consola del navegador, pero el flujo principal del programa no lo «vio».
6.1.2. Ejemplo 2: Callbacks de eventos (addEventListener)
El mismo principio se aplica a los manejadores de eventos (clicks, cargas, etc.).
<button id="miBoton">Haz clic</button>
<script>
console.log("--- Inicio Ejemplo 2: Event Listener ---");
const miBoton = document.getElementById('miBoton');
try {
miBoton.addEventListener('click', () => {
console.log("\nBotón clicado (dentro del listener asíncrono).");
// Este error NO será capturado por el try...catch externo.
console.log(otroErrorAsincrono); // ERROR: ReferenceError
});
console.log("Listener de clic añadido (síncrono).");
} catch (error) {
console.error("¡Este catch NO capturó el error del evento!");
}
console.log("Código después del try...catch (síncrono).");
</script>
Lenguaje del código: HTML, XML (xml)
Explicación:
- El
try...catchenvuelve la adición delevent listener, lo cual es una operación síncrona y no genera un error. - Cuando el usuario hace clic en el botón más tarde, la función del
event listenerse ejecuta de forma asíncrona. - En ese momento futuro,
console.log(otroErrorAsincrono)lanza elReferenceError, pero eltry...catchoriginal ya ha terminado y no puede interceptarlo.
6.2. La solución: try...catch dentro del contexto asíncrono
Para capturar errores en código asíncrono, el try...catch debe estar directamente dentro del bloque de código que se ejecuta asíncronamente.
6.2.1. Solución con setTimeout (y otros callbacks)
console.log("\n--- Inicio Ejemplo 3: setTimeout con try...catch interno ---");
setTimeout(() => {
try {
// Este try...catch SÍ capturará el error.
console.log("\nEjecutando callback de setTimeout (asíncrono) con try...catch.");
console.log(variableInexistenteCapturada); // ESTO LANZARÁ UN ERROR
} catch (error) {
console.error("¡Éxito! Este catch SÍ capturó el error. Mensaje:", error.message);
console.error("Stack trace:", error.stack);
}
}, 100);
console.log("Código después del setTimeout (síncrono).");
/*
Salida esperada (orden aproximado):
--- Inicio Ejemplo 3: setTimeout con try...catch interno ---
Código después del setTimeout (síncrono).
Ejecutando callback de setTimeout (asíncrono) con try...catch.
¡Éxito! Este catch SÍ capturó el error. Mensaje: variableInexistenteCapturada is not defined
Stack trace: ...
*/
Lenguaje del código: JavaScript (javascript)
Explicación:
- El
try...catchahora está dentro del callback desetTimeout. - Cuando el callback se ejecuta asíncronamente, el
try...catchinterno está activo y listo para interceptar cualquier error que ocurra dentro de ese callback.
7. Manejo de errores en Promesas y async/await.
Con la llegada de las Promesas y, más recientemente, async/await, el manejo de errores asíncronos se volvió mucho más legible y manejable, ya que estas características fueron diseñadas para tener un mecanismo de propagación de errores integrado.
- Promesas:
- Usan el método
.catch()para manejar errores, que es el equivalente asíncrono detry...catch.
- Usan el método
async/await:- Permite usar
try...catchde forma directa y legible con código asíncrono que devuelve promesas, haciendo que se vea y se sienta casi como el manejo de errores síncronos. Esto es lo que la primera frase de este punto adelantaba.
- Permite usar
7.1. Promesas y el método catch() para manejar errores asíncronos.
Como vimos, el try...catch tradicional no puede capturar errores en callbacks asíncronos que se ejecutan en un futuro «tick» del event loop. Es aquí donde las Promesas revolucionaron la asincronía y, con ella, el manejo de errores.
¿QUÉ ES UNA PROMESA?
Una Promesa es un objeto que representa la eventual finalización o falla de una operación asíncrona. En lugar de pasar callbacks anidados (lo que lleva al famoso «callback hell»), una Promesa permite encadenar operaciones y, lo que es crucial para nuestro tema, ofrece un mecanismo para el manejo de errores: el método .catch().
🔑El propósito del método .catch()
- Manejo de errores en la cadena de Promesas: El método
.catch()está diseñado específicamente para capturar errores (o «rechazos») que ocurren en cualquier punto de una cadena de Promesas. - Centralización del manejo: Permite tener un único punto de manejo de errores para una secuencia de operaciones asíncronas, en lugar de envolver cada callback individualmente en un
try...catch.
7.1.1 Funcionamiento de .catch()
Cuando una Promesa se «rechaza» (rejects), significa que la operación asíncrona asociada a ella falló. En ese momento, el control salta a la función de callback que le has proporcionado al método .catch() más cercano en la cadena. Este callback recibe el valor con el que la Promesa fue rechazada (generalmente, una instancia de Error).
SINTAXIS BÁSICA USANDO catch()
somePromise()
.then(resultado => {
// Haz algo con el resultado exitoso
})
.catch(error => {
// Maneja el error que ocurrió en 'somePromise' o en cualquier 'then' anterior
});Lenguaje del código: JavaScript (javascript)
Explicación:
somePromise()
Es una función que devuelve una promesa, es decir, un valor que puede estar disponible ahora, más adelante o nunca.
👉 Ejemplo real:fetch('https://api.com/datos').then(resultado => { ... })
Se ejecuta solo si la promesa se resuelve exitosamente (es decir, si no hubo error).
✅ Aquí manejás el resultado positivo, como por ejemplo mostrar los datos recibidos..catch(error => { ... })
Se ejecuta si ocurre un error:- En la propia promesa (
somePromise) - O en cualquier
.then()anterior (por ejemplo, si hay un error de lógica o de código)
- En la propia promesa (
🔄 Flujo simplificado:
- Intentamos realizar una acción asíncrona (
somePromise()). - Si todo sale bien, se ejecuta el bloque
.then(...). - Si algo falla, se salta el
.then(...)y se ejecuta directamente el.catch(...).
7.1.2. Ejemplo para entender .catch().
const revisarTemperatura = new Promise((resolve, reject) => {
const temperatura = 40;
if (temperatura < 30) {
resolve("✅ Temperatura agradable");
} else {
reject("🔥 Hace demasiado calor");
}
});
revisarTemperatura
.then((mensaje) => {
console.log(mensaje);
})
.catch((error) => {
console.log("⚠️ Error:", error);
});Lenguaje del código: JavaScript (javascript)
Explicación paso a paso:
- Creamos una promesa llamada
revisarTemperatura:- Si la temperatura es menor a 30, usamos
resolve(...)y la promesa se cumple. - Si no, usamos
reject(...)y la promesa falla.
- Si la temperatura es menor a 30, usamos
- Usamos
.then()para definir qué pasa cuando todo va bien (la promesa se resuelve). - Usamos
.catch()para manejar el error si algo falla (la promesa es rechazada).
7.2. async/await y try...catch: La combinación ganadora para Errores Asíncronos
Si el .catch() de las Promesas fue un gran avance, la combinación de async/await con try...catch es, sin duda, la forma más elegante y legible de manejar errores en código asíncrono en JavaScript moderno.
Es la «combinación ganadora» porque te permite escribir código asíncrono que se ve y se siente casi como código síncrono, lo que facilita enormemente la lectura y el manejo de errores.
7.2.1. ¿Cómo funciona async/await con try...catch?
Recuerda que async/await es «azúcar sintáctico» sobre Promesas. Esto significa que, bajo el capó, sigue usando el mecanismo de Promesas, pero lo presenta de una manera que es más fácil de entender para nosotros, los humanos.
La magia ocurre con la palabra clave await:
- Cuando usas
awaitdelante de una Promesa, la ejecución de tu funciónasyncse pausa hasta que esa Promesa se resuelve (con éxito) o se rechaza (con un error).- Si la Promesa se resuelve,
awaitte devuelve el valor de resolución. - Si la Promesa se rechaza,
awaitlanza el valor de rechazo como si fuera un error síncrono.
- Si la Promesa se resuelve,
🚀¡Aquí es donde try...catch entra en juego! Debido a que await lanza el error de la Promesa rechazada de forma síncrona, puedes envolver tus llamadas await dentro de un bloque try...catch estándar para capturar esos errores.
SINTAXIS DE async/await con try...catch
async function miFuncionAsincrona() {
try {
// Código asíncrono con await que podría fallar
const resultado = await algunaPromesaQuePuedeFallar();
console.log(resultado);
} catch (error) {
// Captura el error lanzado por await si la Promesa se rechaza
console.error("Error capturado:", error.message);
}
}Lenguaje del código: JavaScript (javascript)
Explicación:
-
async function miFuncionAsincrona() { ... }- Esta línea declara una función asíncrona usando la palabra clave
async. - Esto significa que dentro de esta función puedes usar
awaity que la función siempre devuelve una promesa, incluso si tú no la retornás explícitamente.
- Esta línea declara una función asíncrona usando la palabra clave
try { ... } catch (error) { ... }- Aquí usás un bloque
try...catch, que es una estructura de manejo de errores. - El código que puede producir errores se escribe dentro del
try. - Si ocurre un error dentro del
try, se captura en elcatch.
- Aquí usás un bloque
const resultado = await algunaPromesaQuePuedeFallar();- La palabra clave
awaitpausa la ejecución hasta que la promesaalgunaPromesaQuePuedeFallar():- Se resuelve exitosamente, en cuyo caso su valor se asigna a la variable
resultado. - Se rechaza (falla), en cuyo caso se lanza un error que será atrapado por el bloque
catch.
- Se resuelve exitosamente, en cuyo caso su valor se asigna a la variable
- La palabra clave
console.log(resultado);- Si todo va bien, muestra el resultado de la promesa en la consola.
catch (error) { console.error("Error capturado:", error.message); }- Si ocurre un error, lo atrapás aquí y lo imprimís en la consola con un mensaje personalizado.
7.2.2. Ejemplos prácticos de async/await con try...catch
// Función que devuelve una Promesa
function obtenerUsuario() {
return new Promise((resolve, reject) => {
const exito = true; // Cambia esto a false para simular un error
setTimeout(() => {
if (exito) {
resolve("👤 Usuario encontrado");
} else {
reject(new Error("❌ Usuario no encontrado"));
}
}, 1000);
});
}
// Función async que usa try...catch para manejar errores
async function mostrarUsuario() {
try {
const usuario = await obtenerUsuario();
console.log(usuario); // Muestra el resultado si la promesa se resuelve
} catch (error) {
console.error("Ocurrió un error:", error.message); // Maneja el error si la promesa se rechaza
}
}
mostrarUsuario(); // Ejecutamos la funciónLenguaje del código: JavaScript (javascript)
Explicación paso a paso
obtenerUsuario():- Es una función que simula una operación asíncrona (como una consulta a una base de datos).
- Devuelve una Promesa que:
- Se resuelve con éxito tras 1 segundo si
exitoestrue. - Se rechaza con un error si
exitoesfalse.
- Se resuelve con éxito tras 1 segundo si
mostrarUsuario():- Es una función
async, lo que permite usarawaitdentro. - Dentro del
try, usamosawait obtenerUsuario(), lo que pausa la ejecución hasta que la Promesa se resuelve o se rechaza. - Si todo va bien, el resultado se guarda en
usuarioy se imprime. - Si algo falla, el
catchcaptura el error y lo muestra.
- Es una función
mostrarUsuario()se ejecuta y vemos en consola el resultado exitoso o el mensaje de error.
EN SÍNTESIS: Este patrón (async/await + try...catch) es muy común para trabajar con llamadas a APIs, lectura de archivos, consultas a bases de datos y cualquier otra operación asíncrona en JavaScript moderno.
8. Buenas prácticas en el manejo de errores
8.1. Centralización del Manejo de Errores
La centralización del manejo de errores significa tener un punto único (o unos pocos puntos bien definidos) donde todos los errores no capturados de tu aplicación son interceptados y procesados. Esto es crucial para:
- Registro global (Logging): Enviar todos los errores a un sistema de monitoreo.
- Reportes de errores: Notificar a tu equipo sobre los problemas en producción.
- Mensajes genéricos al usuario: Mostrar un mensaje amigable cuando ocurre algo inesperado, sin exponer detalles técnicos.
- Recuperación a nivel de aplicación: Intentar un reinicio suave o un estado de emergencia.
✅MANEJO DE ERROR CON window.onerror (Navegador)
En entornos de navegador, window.onerror es un evento global que se dispara cuando ocurre un error de JavaScript no capturado en el hilo principal del DOM. Es tu última línea de defensa antes de que el usuario vea una consola con un Uncaught Error.
// Ejemplo de window.onerror
// Asegúrate de que este script se cargue temprano en tu HTML
// para que pueda atrapar la mayoría de los errores.
window.onerror = function(message, source, lineno, colno, error) {
console.error("¡Error global no capturado en el navegador!");
console.error("Mensaje:", message);
console.error("Archivo:", source);
console.error("Línea:", lineno);
console.error("Columna:", colno);
console.error("Objeto Error (si disponible):", error);
// Aquí es donde enviarías el error a un servicio de monitoreo (ej. Sentry, Bugsnag)
// reportErrorToMonitoringService({ message, source, lineno, colno, error });
// Devolver 'true' evita que el navegador muestre su mensaje de error predeterminado.
// Devolver 'false' (o no devolver nada) permite que el navegador muestre el error.
return true; // Generalmente querrás manejarlo tú mismo.
};
// Provocando un error no capturado para probarlo
setTimeout(() => {
// Este ReferenceError no está dentro de un try...catch,
// así que window.onerror debería atraparlo.
console.log(variableQueNoExiste);
}, 100);
// Un error de sintaxis no será capturado por window.onerror,
// ya que ocurre en la fase de parseo antes de la ejecución.
// function() { // Esto es un SyntaxError
// console.log("Syntax error");
// }
Lenguaje del código: JavaScript (javascript)
Explicación:
- Asignamos una función a
window.onerror. Esta función recibirá detalles sobre el error: su mensaje, el archivo de origen, el número de línea/columna y el objetoErroren sí. - Cualquier
ReferenceError,TypeError, etc., que ocurra fuera de untry...catch(como el delsetTimeout) será interceptado por esta función. - La centralización aquí significa que no tienes que poner
try...catchen cada posible punto de fallo de alto nivel.
✅MANEJO DE ERROR CON process.on('uncaughtException') y process.on('unhandledRejection') (Node.js)
En Node.js, tenemos eventos específicos del objeto process para errores no manejados, que son vitales para mantener tu servidor o proceso en pie.
process.on('uncaughtException'): Captura errores síncronos que no fueron capturados por ningúntry...catch. Si no se maneja, ununcaughtExceptionusualmente lleva al cierre del proceso de Node.js.process.on('unhandledRejection'): Captura rechazos de Promesas que no tienen un manejador.catch()adjunto. Si no se maneja, ununhandledRejectiontambién puede llevar al cierre del proceso.
// Ejemplo de centralización de errores en Node.js
// Siempre coloca estos manejadores al inicio de tu aplicación.
process.on('uncaughtException', (error) => {
console.error("\n--- ¡ERROR SÍNCRONO NO CAPTURADO! ---");
console.error("Error:", error.message);
console.error("Stack Trace:\n", error.stack);
// IMPORTANTE: Después de registrar, es buena práctica CRASHear la aplicación de forma controlada.
// El estado del proceso puede ser inconsistente después de un uncaughtException.
// Considera un reinicio automático (ej. con pm2) para esto.
process.exit(1); // Sale con código de error
});
process.on('unhandledRejection', (reason, promise) => {
console.error("\n--- ¡RECHAZO DE PROMESA NO MANEJADO! ---");
console.error("Razón del rechazo:", reason); // Lo que se pasó a reject()
console.error("La promesa que fue rechazada:", promise);
// A diferencia de uncaughtException, un unhandledRejection no siempre
// requiere salir del proceso, pero es una señal de que algo se te escapó.
// ¡Siempre debes tener un .catch() en tus promesas!
// Aquí también podrías enviar a tu servicio de monitoreo.
});
// --- Probando uncaughtException ---
console.log("Probando uncaughtException...");
function causarErrorSincrono() {
// Este error NO está en un try...catch
console.log(variableNodeNoExiste);
}
// causaErrorSincrono(); // Descomenta para probar el uncaughtException
// --- Probando unhandledRejection ---
console.log("Probando unhandledRejection...");
new Promise((resolve, reject) => {
setTimeout(() => {
// Esta promesa se rechaza sin un .catch() adjunto
reject(new Error("¡Fallo en una promesa sin catch!"));
}, 500);
});
// Este código se seguirá ejecutando, a menos que un uncaughtException detenga el proceso.
console.log("El programa principal de Node.js continúa (por ahora)...");
Lenguaje del código: JavaScript (javascript)
Explicación:
- Los escuchadores de
processte permiten capturar errores que, de otra forma, romperían tu aplicación Node.js. - Para
uncaughtException, la práctica recomendada es registrar el error y luego terminar el proceso (process.exit(1)). Esto se debe a que, después de un error síncrono no capturado, el estado de tu aplicación puede ser impredecible e inconsistente. - Para
unhandledRejection, aunque no siempre es necesario salir, es una fuerte indicación de que te faltó un.catch()en algún lugar, y debes arreglarlo.
8.2. Registro de información sobre errores (Logging)
console.error() es genial para desarrollo, pero en producción, necesitas un sistema de logging más robusto. El logging de errores es el proceso de registrar información sobre los errores que ocurren, de manera que puedan ser revisados, analizados y alertados.
Por qué ir más allá de console.error():
console.error()solo muestra en la consola donde se ejecuta la app (servidor o navegador del usuario).- No hay persistencia (los logs se pierden al cerrar la consola).
- No hay agregación (difícil ver patrones de errores en múltiples usuarios/servidores).
- No hay alertas (nadie te avisa si algo grave ocurre).
✅Herramientas de monitoreo de errores (para Producción):
- Sentry:
- Sentry es muy popular, robusto y versátil. Ofrece SDKs para casi cualquier plataforma JS (frontend, backend).
- Bugsnag:
- Bugsnag es otra excelente opción con características similares a Sentry.
- LogRocket:
- LogRocket va un paso más allá y graba sesiones de usuario, permitiéndote «reproducir» lo que el usuario estaba haciendo cuando ocurrió el error. Esto es invaluable para depuración.
✅Estas herramientas te ofrecen:
- Agregación: Recopilan errores de todas las instancias de tu aplicación.
- Contexto: Capturan automáticamente información útil como el navegador del usuario, sistema operativo, versión del código, ID de usuario, etc.
- Alertas: Te notifican por email, Slack, etc., cuando ocurren nuevos errores o picos de errores.
- Gestión: Te permiten marcar errores como resueltos, ignorados, o asignarlos a un equipo.
- Mapas de fuente: Desofuscan tu código minificado para que los
stack tracessean legibles
EJEMPLO:
// Ejemplo conceptual de integración con un servicio de monitoreo (usando Sentry SDK)
// Esto requiere que instales el SDK de Sentry (ej. npm install @sentry/node o @sentry/browser)
// En tu archivo de configuración o al inicio de tu app
import * as Sentry from '@sentry/node'; // o '@sentry/browser' para frontend
Sentry.init({
dsn: "https://[email protected]/12345", // Tu DSN (Data Source Name) de Sentry
// Otras configuraciones, ej. nivel de entorno (production, development)
environment: process.env.NODE_ENV || 'development',
release: '[email protected]' // Versión de tu aplicación
});
// Luego, en tus bloques catch o manejadores globales
try {
// ... código que puede fallar ...
throw new Error("Algo salió mal en una operación crítica.");
} catch (error) {
console.error("Error capturado localmente:", error.message);
Sentry.captureException(error); // Envía el objeto Error a Sentry
}
// O en tu manejador global (ej. window.onerror en frontend)
window.onerror = function(message, source, lineno, colno, error) {
Sentry.captureException(error || new Error(message)); // Captura el error
return true;
};
// Y en Node.js para unhandledRejection y uncaughtException
process.on('unhandledRejection', (reason, promise) => {
Sentry.captureException(reason);
});
process.on('uncaughtException', (error) => {
Sentry.captureException(error);
process.exit(1);
});Lenguaje del código: JavaScript (javascript)
Explicación:
- Se inicializa el SDK de Sentry con tu DSN y otras opciones.
- En cualquier
catchlocal, además de hacerconsole.error, llamas aSentry.captureException(error)para enviar el error al servicio de monitoreo. - Lo mismo se hace en los manejadores de errores globales (
window.onerror,process.on(...)), asegurando que todos los errores, incluso los no capturados, lleguen a tu sistema de monitoreo.
8.3. Re-lanzar Errores (throw error;)
A veces, capturas un error en un nivel bajo de tu aplicación (ej., una función auxiliar) pero sabes que no tienes toda la información o el contexto para manejarlo completamente allí. En estos casos, puedes re-lanzar el error después de haber hecho algo con él (como registrarlo o añadirle contexto).
Esto permite que un manejador de errores de nivel superior (más «arriba» en la cadena de llamadas) tenga la oportunidad de actuar sobre el mismo error.
EJEMPLO:
console.log("\n--- Inicio Ejemplo 5.3: Re-lanzar Errores ---");
class ErrorDeValidacion extends Error {
constructor(message, campo) {
super(message);
this.name = "ErrorDeValidacion";
this.campo = campo;
}
}
// Función de bajo nivel que valida datos
function validarDatosDeUsuario(usuario) {
try {
if (!usuario || !usuario.nombre) {
throw new ErrorDeValidacion("El nombre del usuario es obligatorio.", "nombre");
}
if (!usuario.edad || usuario.edad < 18) {
throw new ErrorDeValidacion("La edad debe ser al menos 18.", "edad");
}
console.log("Datos de usuario válidos.");
return true;
} catch (error) {
// Paso 1: Registrar el error en el nivel local para depuración
console.warn(`[VALIDACION-WARN] Fallo de validación en campo '${error.campo}': ${error.message}`);
// Paso 2: Re-lanzar el error para que un nivel superior lo maneje
throw error; // ¡IMPORTANTE: re-lanza el MISMO objeto error para conservar el stack trace!
}
}
// Función de alto nivel que usa la función de bajo nivel
async function registrarNuevoUsuario(userData) {
try {
console.log("\nIntentando registrar nuevo usuario...");
const esValido = validarDatosDeUsuario(userData); // Esto puede lanzar un error
// Simular operación asíncrona después de la validación
await new Promise(resolve => setTimeout(resolve, 100));
console.log("Usuario registrado con éxito!");
} catch (error) {
// Paso 3: Manejar el error re-lanzado en un nivel superior
console.error("\n[APP-ERROR] No se pudo registrar el usuario.");
if (error instanceof ErrorDeValidacion) {
console.error(`Error de validación final: Campo '${error.campo}' - ${error.message}`);
// Aquí podrías mostrar un mensaje específico al usuario.
} else {
console.error("Error inesperado al registrar:", error.message);
}
}
}
// Prueba 1: Datos inválidos (edad)
registrarNuevoUsuario({ nombre: "Alice", edad: 16 });
// Prueba 2: Datos inválidos (nombre)
registrarNuevoUsuario({ nombre: null, edad: 25 });
// Prueba 3: Datos válidos
registrarNuevoUsuario({ nombre: "Bob", edad: 22 });
console.log("\n--- Fin Ejemplo 5.3 ---");
Lenguaje del código: JavaScript (javascript)
Explicación:
- La función
validarDatosDeUsuariotiene untry...catchinterno. Cuando un error de validación ocurre, lo registra localmente (console.warn) para fines de depuración en ese módulo. - Luego,
throw error;re-lanza el mismo objetoerror. Es crucial re-lanzar el mismo objeto para preservar elstack traceoriginal. Si lanzarasthrow new Error(error.message), perderías el stack trace del origen. - La función de nivel superior
registrarNuevoUsuariotiene su propiotry...catchque intercepta el error re-lanzado. Aquí puede manejar el error de una manera más global, por ejemplo, mostrando un mensaje al usuario final.
8.4. Documentación y mensajes claros
Los errores no son solo para ti; son para tus colegas, para tu yo futuro, y a veces, para tus usuarios.
- Mensajes para Desarrolladores:
- Sean específicos:
TypeError: Cannot read properties of undefined (reading 'id')es mejor queError: Something went wrong. - Indiquen el contexto: Si el error ocurre al procesar un ID, el mensaje podría ser «Failed to retrieve user by ID: 12345. User not found.»
- Proporcionen soluciones o pasos: Si un error es causado por una configuración incorrecta, el mensaje podría sugerir «Check environment variable API_KEY.»
- Usen clases de error personalizadas: Como vimos en 3.2,
UsuarioNoEncontradoErrores más informativo que unErrorgenérico.
- Sean específicos:
- Mensajes para Usuarios Finales:
- Sean amigables: «Ocurrió un problema inesperado» o «No pudimos cargar su perfil en este momento.»
- No expongan detalles técnicos: Nunca muestres
stack traceso detalles internos al usuario. Esto es una vulnerabilidad de seguridad y confunde. - Ofrezcan una acción: «Por favor, intente de nuevo más tarde» o «Contacte a soporte técnico con el ID de referencia: XYZ.»
- Localización (i18n): En aplicaciones multi-idioma, los mensajes de error al usuario deben estar traducidos.
EJEMPLO:
// Ejemplo de mensajes claros
class ErrorDeConexionApi extends Error {
constructor(url, statusCode, message = `Error de conexión a la API ${url}`) {
super(message);
this.name = "ErrorDeConexionApi";
this.url = url;
this.statusCode = statusCode;
this.userMessage = "Lo sentimos, no pudimos cargar la información en este momento. Por favor, intente de nuevo más tarde.";
if (statusCode === 404) {
this.userMessage = "El recurso solicitado no fue encontrado. Verifique la URL.";
}
}
}
async function cargarDatosUsuario(userId) {
try {
const response = await fetch(`https://api.myapp.com/users/${userId}`);
if (!response.ok) {
// Lanzamos nuestro error específico con detalles para el desarrollador
throw new ErrorDeConexionApi(response.url, response.status,
`La petición a ${response.url} falló con estado ${response.status}.`);
}
return await response.json();
} catch (error) {
if (error instanceof ErrorDeConexionApi) {
// Para el desarrollador:
console.error(`[DEV_ERROR] ${error.name} (${error.statusCode}) en ${error.url}: ${error.message}\n${error.stack}`);
// Para el usuario:
mostrarMensajeAlUsuario(error.userMessage);
} else {
console.error(`[DEV_ERROR] Error inesperado: ${error.message}\n${error.stack}`);
mostrarMensajeAlUsuario("Ocurrió un problema desconocido. Intente refrescar la página.");
}
}
}
function mostrarMensajeAlUsuario(mensaje) {
const statusDiv = document.getElementById('status-message');
if (statusDiv) {
statusDiv.textContent = mensaje;
statusDiv.style.color = 'red';
} else {
alert(mensaje);
}
}
// Simular uso
// Cargar datos de usuario que existe (simulado)
// cargarDatosUsuario("123");
// Cargar datos de usuario que falla (simulado 404)
// cargarDatosUsuario("999"); // Asume que un 404 ocurre aquí.
// Simular un elemento en el DOM para el mensaje
// document.body.innerHTML += '<div id="status-message"></div>';
// cargarDatosUsuario("999");
Lenguaje del código: JavaScript (javascript)
Explicación:
- La clase
ErrorDeConexionApino solo tiene un mensaje para el desarrollador, sino también una propiedaduserMessageque es más amigable y puede adaptarse según el tipo de error (ej., 404). - En el
catch, separamos el logging para el desarrollador (con detalles técnicos y stack trace) del mensaje mostrado al usuario final.
8.5. Pruebas de errores (Testing)
Si quieres que tu manejo de errores sea fiable, tienes que probarlo. ¿Cómo?:
- Pruebas unitarias: Verifica que tus funciones o módulos lancen los errores correctos bajo las condiciones esperadas.
- Pruebas de integración: Asegúrate de que las cadenas de Promesas o las secuencias
async/awaitmanejen y propaguen los errores como se espera a través de los diferentes componentes. - Simulación de fallos: En tus pruebas, puedes simular fallos de red, datos inválidos, errores de base de datos, etc., para forzar tus manejadores de errores a activarse.
8.6. No dejes caer tu aplicación (Degradación Elegante)
La degradación elegante es una filosofía de diseño: tu aplicación debe seguir funcionando (aunque sea con funcionalidad reducida) incluso si ocurren errores graves. No debe simplemente colapsar.
- Evitar una pantalla en blanco: Si un componente falla al cargar datos, el resto de la página debería seguir siendo utilizable.
- Mensajes de fallback: Mostrar un mensaje tipo «No se pudo cargar esta sección» en lugar de un error.
- Funcionalidad reducida: Si un servicio secundario falla, la funcionalidad principal de tu aplicación aún debería estar operativa.
8.7. Evitar bloques catch vacíos:
Esta es una regla de oro simple pero muy importante: Nunca dejes un bloque catch vacío o con solo un console.log(error) genérico.
Un catch vacío:
- Oculta problemas: Silencia errores que deberían ser tratados, depurados o al menos registrados.
- Genera código impredecible: Si no manejas un error, tu código puede seguir ejecutándose en un estado inconsistente.
- Impide la depuración: Si un error no se registra ni se re-lanza, es imposible saber que ocurrió en producción.
EJEMPLO:
console.log("\n--- Inicio Ejemplo 5.7: Evitar Empty Catch Blocks ---");
function realizarOperacionCritica(data) {
try {
if (!data) {
throw new Error("Datos no proporcionados para operación crítica.");
}
// Simular operación que puede fallar
const resultado = JSON.parse(data); // Esto fallará si 'data' no es JSON válido
console.log("Operación crítica exitosa:", resultado);
} catch (error) {
// MALA PRÁCTICA: Empty catch block (o solo console.log(error))
// console.error("Error en operación crítica:", error); // Esto es mejor que vacío, pero aún no óptimo
// BUENA PRÁCTICA: Siempre haz algo significativo
// 1. Loggea el error con detalles (quizás a un sistema de monitoreo)
console.error(`[CRITICAL_ERROR] Fallo en operación crítica: ${error.message}\n${error.stack}`);
// Sentry.captureException(error); // Imagina enviar esto a Sentry
// 2. Decide si re-lanzar o manejar completamente
// Si puedes recuperarte, no re-lances.
// Si el programa no puede continuar de forma significativa, re-lanza.
if (error instanceof SyntaxError && error.message.includes('JSON')) {
console.error("El dato proporcionado no es un JSON válido. No se puede procesar.");
// Aquí podríamos mostrar un mensaje al usuario o retornar un valor predeterminado seguro.
return null; // Recuperación: retorna null para indicar que falló pero no rompió todo.
} else {
// Para otros tipos de errores que no sabes manejar aquí, re-lanza.
throw error;
}
}
}
// Ejemplo de uso:
try {
realizarOperacionCritica('{"clave": "valor"}'); // Éxito
realizarOperacionCritica('{"clave": "valor",'); // Error de JSON (SyntaxError) -> será loggeado y devolverá null
realizarOperacionCritica(null); // Error de "datos no proporcionados" -> será loggeado y re-lanzado
} catch (error) {
console.error("\n[GLOBAL_HANDLER] Error capturado en el nivel superior:", error.message);
}
console.log("\n--- Fin Ejemplo 5.7 ---");
Lenguaje del código: JavaScript (javascript)
Explicación:
- El primer
catchestá comentado para mostrar la «mala práctica». - El segundo
catches un ejemplo de «buena práctica»:- Siempre registra el error con suficiente detalle (nombre, mensaje, stack trace, y contexto si es posible).
- Luego, toma una decisión: ¿Puedes recuperarte de este error aquí?
- Si sí (como con el
SyntaxErrorde JSON), haz lo necesario (ej.,return nullo un valor por defecto) y no re-lances. - Si no, y un nivel superior debería manejarlo, re-lanza el error para que no se pierda.
- Si sí (como con el
9. Preguntas frecuentes y resumen sobre errores en JS.
Los errores se manejan con bloques try...catch, permitiendo capturar y responder ante fallas sin detener el programa. También se puede lanzar errores con throw y manejarlos adecuadamente para mantener el flujo controlado.
Un bloque try...catch permite ejecutar código dentro de try y capturar cualquier error que ocurra dentro, mediante catch. Esto evita que errores detengan la ejecución y permite una respuesta programada.
La palabra clave throw permite lanzar errores personalizados. Se usa junto con un mensaje o un objeto Error. Esto permite que los errores se manejen explícitamente dentro de un bloque try...catch.
El objeto Error representa un error en JavaScript. Se crea con new Error('mensaje') y puede incluir información como nombre y mensaje. Es útil para lanzar errores detallados que se pueden capturar y manejar.
En promesas, los errores se manejan con .catch(). En funciones async, se usa try...catch dentro de la función para atrapar errores en await. Esto permite tratar errores asíncronos igual que los síncronos.
finally es una sección que se ejecuta siempre, ocurra o no un error. Va después de catch y se usa para liberar recursos, cerrar conexiones o ejecutar tareas que deben completarse pase lo que pase.
window.onerror permite capturar errores globales en tiempo de ejecución. Se asigna una función que recibe detalles del error. Es útil como mecanismo de registro de errores en producción, pero no reemplaza try...catch.
Algunas buenas prácticas son usar try...catch en puntos críticos, lanzar errores descriptivos, registrar errores en consola o servicios, y nunca mostrar mensajes técnicos al usuario. También es clave validar entradas y manejar rechazos en promesas.
