try, catch y finally
En resumen: En JavaScript hay un solo catch: dentro de él compruebas el tipo de error con instanceof, y finally se ejecuta siempre, con error o sin él.
Problema
La cuenta para cero personas
El programa divide la cuenta del restaurante a partes iguales. Ya conoces la entrada incorrecta, y la atrapas:
try { const bill = toInt(await ask("")); const people = toInt(await ask("")); console.log(`Por persona: ${Math.trunc(bill / people)} €`);} catch (e) { console.log("Escribe números enteros.");}La cuenta es de 90 €, y alguien escribe 0 como número de personas:
Por persona: Infinity €El programa no se detiene, pero imprime un disparate. En JavaScript la división entre cero no lanza. Da Infinity, así que catch no tiene nada que atrapar. Y cuando los errores sí son de tipos distintos, sigue habiendo un solo catch. Entonces, ¿cómo le dices al cliente qué es exactamente lo que está mal?
La idea
Un solo catch, varios tipos
El cero no es una excepción, así que es un if:
try { const bill = toInt(await ask("")); const people = toInt(await ask("")); if (people === 0) { console.log("Las personas no pueden ser 0."); } else { console.log(`Por persona: ${Math.trunc(bill / people)} €`); }} catch (e) { console.log("Escribe números enteros.");}| Entrada | Salida |
|---|---|
| 90 y 3 | Por persona: 30 € |
| 90 y 0 | Las personas no pueden ser 0. |
| 90 y tres | Escribe números enteros. |
Pero hay errores que JavaScript lanza por sí solo. El recibo tiene una línea de guiones, y su longitud se escribe a mano. "-".repeat(-1) lanza un RangeError, y toInt lanza un Error. Dos tipos, pero después de try hay un solo catch. Dentro de él compruebas el tipo con instanceof («es del tipo»):
const text = await ask("");try { const width = toInt(text); console.log("-".repeat(width));} catch (e) { if (e instanceof RangeError) { console.log("La longitud no puede ser negativa."); } else { console.log("Escribe un número entero."); }}| Entrada | Salida |
|---|---|
| 5 | ----- |
| -1 | La longitud no puede ser negativa. |
| cinco | Escribe un número entero. |
Las comprobaciones van de arriba abajo, como cualquier if / else if. Se ejecuta la primera rama cuyo tipo coincide, y solo esa.
const text = await ask("");try { const width = toInt(text); console.log("-".repeat(width));} catch (e) { if (e instanceof RangeError) { console.log("La longitud no puede ser negativa."); } else { console.log("Escribe un número entero."); }}Paso a paso, en palabras
- Primera ejecución: como longitud de la línea se ha escrito «cinco».
- toInt no puede sacar un número de «cinco»: se lanza Error.
- La primera comprobación es para RangeError. No coincide, se salta.
- Coincide el else. Se ejecuta él y solo él.
- Segunda ejecución: se ha escrito «-1».
- toInt da -1, y "-".repeat(-1) lanza RangeError.
- Ya coincide la primera comprobación. Al else no se llega.
- Las comprobaciones van de arriba abajo. Se ejecuta la primera que coincide.
Más precisamente, para curiosos
- Lo que no quieres atrapar aquí, lo vuelves a lanzar en
else:throw e;. Cómo funcionathrowestá en la lección siguiente.
La idea
La familia de las excepciones
Los errores son clases, relacionadas por herencia. Aquí están las más comunes:
Error├── TypeError tipo de valor incorrecto: undefined.seats├── RangeError fuera de lo permitido: "-".repeat(-1)├── SyntaxError escritura incorrecta: JSON.parse("{")├── ReferenceError un nombre que no existe├── URIError├── EvalError└── AggregateErrorinstanceof comprueba el tipo que nombras y todas sus clases hijas. Es el polimorfismo del módulo sobre la herencia: un objeto de una clase hija también es del tipo de la clase padre.
const error = new RangeError("Invalid count value: -1");console.log(error instanceof RangeError);console.log(error instanceof Error);console.log(error instanceof TypeError);truetruefalsecatch lo atrapa todo, y e.name te dice el tipo:
function printLine(widthText) { try { console.log("-".repeat(toInt(widthText))); } catch (e) { console.log(`${e.name}: ${e.message}`); }}
printLine("5");printLine("-1");printLine("cinco");-----RangeError: Invalid count value: -1Error: "cinco" no es un número entero.e.name es el nombre del tipo de error: RangeError de repeat, Error de toInt.
Más precisamente, para curiosos
- En JavaScript se puede lanzar cualquier valor, incluso un string, así que en
catchlaeno siempre es unError. Lo verás en la lección siguiente.
- Atrapa de la forma más concreta que puedas. Un catch que no comprueba el tipo también esconde errores que no esperabas, como un fallo en tu propio código.
Pruébalo
¿Qué catch?
¿Qué imprimirá el programa?
const tables = [{ seats: 4 }, { seats: 2 }, { seats: 6 }];try { console.log(`Mesa 4: ${tables[3].seats} sitios`);} catch (e) { if (e instanceof RangeError) { console.log("Fuera de lo permitido"); } else if (e instanceof TypeError) { console.log("Valor incorrecto"); } else { console.log("Error general"); }}tables[3] no lanza. Es undefined. El error llega con .seats: no se puede leer de undefined, y eso es un TypeError. No es un RangeError, así que la primera comprobación no pasa. La segunda pasa y se ejecuta. Al else nunca se llega, aunque también lo atraparía.Pruébalo
Encuentra el error
text = "-1" el programa imprime «Algo salió mal.» y no «La longitud no puede ser negativa.». ¿En qué línea está el error?Elige la línea con el error.
RangeError es una clase hija de Error, así que e instanceof Error también es verdadero para él. La primera comprobación lo atrapa todo, y a la segunda nunca se puede llegar. JavaScript no dice nada: el programa se ejecuta y simplemente imprime el mensaje general. La regla es de lo concreto a lo general: intercambia las dos comprobaciones.La idea
Nadie te obliga
Java tiene excepciones comprobadas: el compilador no acepta el programa hasta que las atrapas o las declaras con throws. JavaScript no tiene ninguna así. Ningún error tiene que atraparse:
function printReceipt() { console.log("Imprimo el recibo..."); console.log("-".repeat(20)); console.log("Listo.");}
printReceipt();Imprimo el recibo...--------------------Listo.repeat lanza un RangeError con una longitud negativa, pero nadie te pide que lo atrapes. Lo mismo pasa con toInt.
Así que la responsabilidad es tuya. Qué errores lanza una función está escrito en su documentación. En MDN, la referencia de JavaScript, es la sección «Exceptions». Léela antes de decidir si atrapar.
La idea
finally: siempre
La impresora de la caja tiene que liberarse después de cada recibo, o el siguiente no se imprimirá. Pase lo que pase, salga bien o haya un error:
function printReceipt(qtyText) { console.log("La impresora está ocupada."); try { const qty = toInt(qtyText); console.log(`Café: ${qty} x 2 € = ${qty * 2} €`); } catch (e) { console.log(`Cantidad incorrecta: ${qtyText}`); } finally { console.log("La impresora está libre."); }}
printReceipt("3");printReceipt("tres");La impresora está ocupada.Café: 3 x 2 € = 6 €La impresora está libre.La impresora está ocupada.Cantidad incorrecta: tresLa impresora está libre.finally se ejecuta siempre: cuando todo sale bien, cuando la excepción se atrapa, e incluso cuando no hay catch o este vuelve a lanzar. En este último caso finally se ejecuta, y la excepción sigue subiendo hasta el siguiente try que pueda atraparla.
¿Por qué no una simple línea después del try/catch? Porque esa línea se salta si la excepción no se atrapa, o si try tiene un return. finally no se salta.
Más precisamente, para curiosos
- También puedes tener
tryconfinallyy sincatch: la excepción no se atrapa, pero la limpieza se ejecuta.
- Java y Python tienen una forma más corta que cierra un archivo o una conexión por sí sola. Node.js 22 no tiene nada así (
usingllega en versiones más nuevas y aquí da unSyntaxError), así que ahí la limpieza va enfinally.
finallyno se ejecuta solo si se detiene todo el programa, por ejemplo conprocess.exit(0).
Pruébalo
finally y return
¿Qué imprimirá el programa?
function reserve(guests) { try { if (guests > 6) { return `No hay mesa para ${guests}`; } return `Mesa para ${guests}`; } finally { console.log("Registré el intento."); }}
console.log(reserve(8));Un return en try no se salta finally. La función ejecuta primero finally, y después devuelve el texto. Por eso «Registré el intento.» se imprime antes del resultado, que console.log recibe solo cuando la función ha terminado.
Ejercicio
Programas con errores distintos
Resuélvelas en tu editor. No imprimas mensajes para pedir datos.
1. Precio por persona. Los precios están en el array [120, 75, 300]. Lee un número de producto (desde 1) y el número de personas, e imprime el precio por persona con división entera. Comprueba con if un producto que no existe (undefined) y cero personas, y atrapa la entrada incorrecta e imprime su mensaje.
| Entrada | Salida |
|---|---|
| 2 y 3 | Por persona: 25 € |
| 5 y 2 | No existe ese producto. |
| 1 y 0 | El número de personas no puede ser 0. |
| 1 y dos | Error: "dos" no es un número entero. |
2. Calculadora. Lee un número entero, un operador (+, -, * o /) y un segundo número entero, cada uno en su propia línea. Imprime la cuenta (la división es Math.trunc(a / b)). Con texto en lugar de un número di «Escribe números enteros.», con división entre cero «División entre cero.», y con cualquier otro operador «Operador desconocido: » seguido del operador. Aquí la división entre cero es un if.
84/484 / 4 = 2184/0División entre cero.Más tareas
- El datáfono. La función
pay(amountText)imprime «El datáfono está conectado.», después «Pagado: X €» o un mensaje sobre la entrada incorrecta, y enfinally«El datáfono está apagado.». Llámalo con «25» y con «veinticinco». - Nota media sin caídas. Lee 5 líneas con notas. Sáltate las que no son números enteros (una excepción) o no están entre 2 y 6 (un
if). Imprime la media con división entera. Si ninguna nota es válida, imprime «No hay notas válidas.» (aquí la división entre cero no lanza, así que es unif). - Predice y comprueba. Escribe un
tryconcatchyfinally, donde cada bloque imprime una línea. Predice la salida cuando sale bien, con una excepción atrapada y con una no atrapada, y después ejecútalo.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Métodos de instancia · Herencia · Polimorfismo · Excepciones
- Continúa en: Lanzar excepciones y crear las tuyas · Archivos · Datos en archivos · Códigos hash y funciones hash
- Ejercicios sobre el tema: Errores y excepciones