Ir al contenido
wannacode

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:

bill.js
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:

Salida
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
  1. Primera ejecución: como longitud de la línea se ha escrito «cinco».
  2. toInt no puede sacar un número de «cinco»: se lanza Error.
  3. La primera comprobación es para RangeError. No coincide, se salta.
  4. Coincide el else. Se ejecuta él y solo él.
  5. Segunda ejecución: se ha escrito «-1».
  6. toInt da -1, y "-".repeat(-1) lanza RangeError.
  7. Ya coincide la primera comprobación. Al else no se llega.
  8. 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 funciona throw está 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:

La jerarquía
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
└── AggregateError

instanceof 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);
Salida
true
true
false

catch 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");
Salida
-----
RangeError: Invalid count value: -1
Error: "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 catch la e no siempre es un Error. 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");
}
}
Respuesta

Pruébalo

Encuentra el error

Con 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.

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();
Salida
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");
Salida
La impresora está ocupada.
Café: 3 x 2 € = 6 €
La impresora está libre.
La impresora está ocupada.
Cantidad incorrecta: tres
La 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 try con finally y sin catch: 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í (using llega en versiones más nuevas y aquí da un SyntaxError), así que ahí la limpieza va en finally.
  • finally no se ejecuta solo si se detiene todo el programa, por ejemplo con process.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));
Respuesta

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.

Entrada
84
/
4
Salida
84 / 4 = 21
Entrada
84
/
0
Salida
Divisió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 en finally «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 un if).
  • Predice y comprueba. Escribe un try con catch y finally, 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

1. El catch tiene un if / else if con instanceof. La excepción coincide con dos de las comprobaciones. ¿Cuál se ejecuta?
2. e es un RangeError. ¿Qué da e instanceof Error?
3. ¿Por qué la comprobación e instanceof Error tiene que ir la última?
4. ¿Cuándo se ejecuta finally?
5. ¿Cuántos bloques catch puede tener un solo try en JavaScript?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·