Menu

try/catch en JavaScript: manejo de errores sin romper la app

Cómo funcionan try/catch/finally en JavaScript: capturar errores, el objeto error, relanzar excepciones y cuándo try/catch no es la mejor opción.

try/catch en JavaScript: una red de seguridad, no un cinturón

Cuando una línea de JavaScript lanza un error, la ejecución se detiene en seco y el error empieza a propagarse hacia arriba por la pila de llamadas. Si nadie lo captura, el programa se cae (en Node) o te deja un muro rojo en la consola (en los navegadores). Para eso está el try/catch: para interceptar ese error y decir "ya sé que esto puede fallar, así que haz esto otro en su lugar".

Así se ve la estructura básica:

index.js
Output
Click Run to see the output here.

JSON.parse lanza un SyntaxError. La ejecución salta de inmediato al bloque catch y el error queda asignado a err. El tercer console.log se sigue ejecutando: el fallo quedó contenido.

Si el bloque try termina sin lanzar nada, el catch se omite por completo. Está ahí únicamente para la ruta de error.

El objeto Error en JavaScript

Lo que se lance se enlaza al parámetro que declares en catch (...). Lo habitual es que sea una instancia de Error con tres campos útiles:

index.js
Output
Click Run to see the output here.

name te dice la subclase (TypeError, RangeError, SyntaxError, etc. — más sobre ellas en el siguiente doc). message es la descripción legible para humanos. stack es el trace completo, que vale oro a la hora de depurar.

Un detalle a tener en cuenta: JavaScript te deja hacer throw de cualquier cosa, no solo de objetos Error. En código antiguo a veces te encuentras cosas como throw "algo falló". Cuando escribas tus propios throw, lanza siempre un Error para que quien lo capture reciba también el stack trace:

index.js
Output
Click Run to see the output here.

El bloque finally se ejecuta pase lo que pase

finally es un tercer bloque opcional que se ejecuta haya o no haya ocurrido un error, y sin importar si el catch lo atrapó. Sirve para tareas de limpieza: cerrar archivos, liberar bloqueos u ocultar un spinner de carga:

index.js
Output
Click Run to see the output here.

El spinner se oculta sin importar si la carga salió bien o falló. Sin finally, tendrías que repetir esa línea en ambas ramas... y seguro que en alguna se te olvida.

El bloque finally se ejecuta incluso cuando dentro del try o del catch hay un return. La función retorna después de que finally termine. A veces sorprende, pero en la mayoría de los casos es justo lo que necesitas.

No siempre hace falta catch

El catch es opcional. Un try/finally sin catch es válido y resulta muy útil cuando quieres garantizar la limpieza pero no tienes intención de manejar el error: lo que buscas es que se propague:

index.js
Output
Click Run to see the output here.

El try/finally interno libera el lock incluso cuando fn() lanza un error, pero no se lo traga: el código que llamó sigue viéndolo. Silenciar errores sin decir nada ("falló y no avisé a nadie") es una de las peores pesadillas a la hora de depurar.

Relanzar errores: maneja unos, deja pasar otros

Un bloque catch no tiene por qué encargarse de todo. Puedes inspeccionar el error, tratar lo que te interese y relanzar el resto:

index.js
Output
Click Run to see the output here.

El patrón con instanceof es claro: identifica los errores de los que sabes recuperarte y deja que el resto siga subiendo por la pila. Tragarse cualquier error con un catch vacío es un code smell — pierdes toda la información útil cuando algo inesperado falla.

try/catch con async/await en JavaScript

Dentro de una función async, las promesas rechazadas que esperas con await se convierten en errores lanzados, y el try/catch las gestiona igual que un error síncrono:

index.js
Output
Click Run to see the output here.

Un detalle sutil: tienes que hacer await de la promesa dentro del bloque try. Si devuelves la promesa sin esperarla, el rechazo ocurre cuando la función ya ha terminado su ejecución, y el catch nunca llega a verlo:

async function bad() {
  try {
    return fetch("/broken");  // no await — caller sees the rejection
  } catch (err) {
    // never runs
  }
}

Regla práctica: dentro de funciones async, pon un await sobre aquello que quieras que cubra el try/catch.

try/catch anidado en JavaScript

Puedes anidar bloques try/catch cuando el código interno y el externo fallan por motivos distintos y quieres manejarlos por separado:

index.js
Output
Click Run to see the output here.

El catch interno gestiona el caso "la forma de los datos no es correcta" devolviendo un valor por defecto seguro. El externo se encarga del caso "la entrada ni siquiera era JSON" envolviendo el error y relanzándolo. Anidar está bien cuando cada capa tiene una estrategia de recuperación distinta; si ambos bloques harían lo mismo, es mejor aplanarlos.

Cuándo no conviene usar try/catch

try/catch es una herramienta para fallos esperados y recuperables. No sirve para tapar bugs.

  • No envuelvas el cuerpo entero de una función "por si acaso". Si no tienes un plan real para el error, deja que se propague: un error sin capturar con su stack trace es mucho más útil que uno silenciado.
  • No lo uses como flujo de control. Los bloques try tienen un coste real y enturbian el código frente a un simple if. if (user) gana a try { user.name } catch {}.
  • No hagas "capturar, loguear e ignorar". Como mínimo, relanza el error o devuelve un valor centinela que quien llame pueda detectar.

La prueba mental: "¿qué hace quien usa este código cuando esto falla?". Si no tienes respuesta, todavía no estás listo para hacer catch del error.

Resumen rápido

  • try { ... } catch (err) { ... } — intercepta errores lanzados.
  • finally { ... } — siempre se ejecuta; úsalo para limpieza.
  • throw new Error("...") — lanza siempre subclases de Error para que el stack trace funcione.
  • throw err; dentro de catch — relanza el error cuando no puedas manejarlo.
  • await dentro de try — imprescindible para que try/catch vea los rechazos de promesas.

Siguiente: tipos de error

TypeError, RangeError, SyntaxError... JavaScript trae toda una familia de clases de error integradas, y saber qué significa cada una hace que capturar y reportar sea mucho más preciso. Eso es lo que veremos en el siguiente documento.

Preguntas frecuentes

¿Cómo funciona try/catch en JavaScript?

Metes el código que puede fallar dentro de try { ... }. Si algo lanza una excepción, la ejecución salta directo al bloque catch (err) { ... }, donde err contiene el valor lanzado. Si no se lanza nada, el catch se ignora. El bloque opcional finally { ... } se ejecuta pase lo que pase, ideal para limpieza de recursos.

¿Cuándo conviene usar try/catch en JavaScript?

Úsalo alrededor de operaciones que realmente pueden fallar en tiempo de ejecución: JSON.parse sobre datos que no controlas, respuestas de fetch, I/O de red o de archivos. No envuelvas cada línea: si no tienes un plan para recuperarte del error, déjalo propagar. Un try/catch genérico alrededor de código que funciona bien esconde bugs en lugar de manejarlos.

¿try/catch captura errores asíncronos?

Solo si haces await de la promesa dentro del try. Una llamada suelta tipo somePromise() no se va a capturar y el error termina como un unhandled rejection. Con async/await, try/catch se comporta igual que con código síncrono. Si trabajas con promesas sin await, usa .catch() en la cadena.

¿Cómo relanzo un error en JavaScript?

Dentro del catch, simplemente throw err; (o lanza un error nuevo que envuelva al original). Es útil cuando quieres manejar ciertos errores y dejar que los demás sigan subiendo: revisas el tipo o mensaje, atiendes lo que puedes y relanzas el resto para que los llamadores de arriba también se enteren.

Aprende a programar con Coddy

COMENZAR