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:
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:
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:
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:
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:
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:
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:
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:
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
trytienen un coste real y enturbian el código frente a un simpleif.if (user)gana atry { 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 deErrorpara que el stack trace funcione.throw err;dentro decatch— relanza el error cuando no puedas manejarlo.awaitdentro detry— imprescindible para quetry/catchvea 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.