Excepciones
En resumen: Una excepción es un objeto que describe un error ocurrido mientras el programa se ejecuta: el stack trace muestra dónde, y try y catch lo atrapan para que el programa pueda seguir.
Problema
12a en lugar de 12
La taquilla del concierto pregunta cuántas entradas quieres. Una entrada cuesta 15 €.
import { createInterface } from "node:readline";
const rl = createInterface({ input: process.stdin });const lines = rl[Symbol.asyncIterator]();async function ask(question) { process.stdout.write(question); return (await lines.next()).value;}
const tickets = Number(await ask("¿Cuántas entradas? "));console.log(`Total: ${tickets * 15} €`);rl.close();Las primeras ocho líneas son ask() de la lección sobre la entrada. Debajo está el propio programa.
Con la entrada 12 el programa dice «Total: 180 €». Pero el cliente tiene prisa y pulsa una letra de más:
¿Cuántas entradas? 12aTotal: NaN €El programa no se detiene, pero tampoco está bien: «NaN €» no es un total. Peor aún, sigue como si todo estuviera bien. ¿Cómo puede saber el programa que la entrada es incorrecta, y qué debe hacer entonces?
La idea
Excepción
Number tiene que devolver un número. No puede sacar un número de «12a», y no lanza ningún error. Devuelve el valor especial NaN («not a number», no es un número). NaN * 15 sigue siendo NaN. Y parseInt es todavía más silencioso: lee «12» y se salta el resto.
console.log(Number("12a"));console.log(Number("12"));console.log(parseInt("12a"));NaN1212JavaScript a menudo sigue con un valor incorrecto en lugar de detenerse. Así que la comprobación es cosa tuya. Aquí está en la función ya hecha toInt (líneas 10 a 16). Por ahora, cópiala; aprenderás a escribirla en la lección siguiente:
import { createInterface } from "node:readline";
const rl = createInterface({ input: process.stdin });const lines = rl[Symbol.asyncIterator]();async function ask(question) { process.stdout.write(question); return (await lines.next()).value;}
function toInt(text) { const number = Number(text); if (text.trim() === "" || !Number.isInteger(number)) { throw new Error(`"${text}" no es un número entero.`); } return number;}
const tickets = toInt(await ask("¿Cuántas entradas? "));console.log(`Total: ${tickets * 15} €`);rl.close();Cuando el texto no es un número entero, toInt crea un objeto que describe el problema y lo lanza. Este objeto se llama excepción (en JavaScript, simplemente error). Si nadie la atrapa, el programa se detiene:
¿Cuántas entradas? 12afile:///projects/tickets/tickets.js:13 throw new Error(`"${text}" no es un número entero.`); ^
Error: "12a" no es un número entero. at toInt (file:///projects/tickets/tickets.js:13:11) at file:///projects/tickets/tickets.js:18:17 at process.processTicksAndRejections (node:internal/process/task_queues:103:5)
Node.js v22.22.2Este texto se llama stack trace, y se lee de arriba abajo:
- Arriba del todo: el archivo y la línea donde se lanzó la excepción, la propia línea, y un
^debajo del lugar. - La línea después de la vacía: el tipo y el mensaje.
Error: "12a" no es un número entero. - Las líneas con «at»: dónde ocurrió. Es la pila de llamadas de la lección de recursión, de dentro hacia fuera. Arriba está
toInt, que lanzó, y debajo está el lugar desde donde se llamó:tickets.js:18. El número que sigue es la columna. Las líneas connode:internalestán en el código de Node.js, y rara vez las necesitas.
Los mensajes del propio lenguaje están en inglés porque los escribe el lenguaje, no tú.
Pruébalo
Encuentra el error
El programa debería imprimir la última nota del examen. Se detiene con esto:
/projects/tickets/scores.js:6console.log(lastScore(scores).toFixed(1)); ^
TypeError: Cannot read properties of undefined (reading 'toFixed') at Object.<anonymous> (/projects/tickets/scores.js:6:30) at Module._compile (node:internal/modules/cjs/loader:1705:14) at Object..js (node:internal/modules/cjs/loader:1838:10) at Module.load (node:internal/modules/cjs/loader:1441:32) at Function._load (node:internal/modules/cjs/loader:1263:12) at TracingChannel.traceSync (node:diagnostics_channel:328:14) at wrapModuleLoad (node:internal/modules/cjs/loader:237:24) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:171:5) at node:internal/main/run_main_module:36:49
Node.js v22.22.2Lee el stack trace. ¿En qué línea del programa está el error?
Elige la línea con el error.
scores[5] no lanza. Da undefined, y la función lo devuelve. El programa se detiene solo en la línea 6, donde se llama a .toFixed(1) sobre undefined: TypeError: Cannot read properties of undefined. Así que en JavaScript el stack trace te dice dónde apareció el error, y a veces el fallo está antes: ¿de dónde salió este undefined? El último índice es scores.length - 1.La idea
try y catch
Una excepción se puede atrapar. El código que podría lanzar va en try. Lo que debe pasar cuando hay un error, lo escribes en catch:
const text = await ask("¿Cuántas entradas? ");try { const tickets = toInt(text); console.log(`Total: ${tickets * 15} €`);} catch (e) { console.log("Eso no es un número entero.");}console.log("¡Gracias!");12a¿Cuántas entradas? Eso no es un número entero.¡Gracias!- Entre los paréntesis de
catchestá el nombre de la excepción, aquíe. Ahí no escribes un tipo:catchen JavaScript atrapa cualquier error. Cómo distinguir los tipos está en la lección siguiente. - Si todo en
trysale bien,catchse salta. Con la entrada12la salida es «Total: 180 €» y «¡Gracias!». - Después de
catchel programa sigue adelante. Ya no se detiene. - A partir de aquí solo mostramos el código entre
toIntyrl.close().
const text = await ask("¿Cuántas entradas? ");try { const tickets = toInt(text); console.log(`Total: ${tickets * 15} €`);} catch (e) { console.log("Eso no es un número entero.");}console.log("¡Gracias!");Paso a paso, en palabras
- El programa pregunta cuántas entradas. Escribes «12a».
- En try: toInt intenta sacar un número de «12a».
- No puede. La función lanza Error.
- La ejecución salta directamente a catch. La línea con «Total» se salta.
- catch atrapa el error (en JavaScript, cualquiera): imprime el mensaje para el cliente.
- Después de catch el programa sigue. No se detiene.
Más precisamente, para curiosos
- Una variable declarada en
try(aquítickets) existe solo hasta la llave de cierre detry. Si también la necesitas después del bloque, declárala antes detry.
- El nombre es opcional:
catch { … }sin paréntesis también funciona, cuando no necesitas el error.
Pruébalo
¿Qué líneas se ejecutan?
¿Qué imprimirá el programa?
console.log("Inicio");try { console.log("Leo"); const seats = toInt("diez"); console.log(`Leí ${seats}`);} catch (e) { console.log("Entrada incorrecta");}console.log("Fin");«Leo» se imprime porque va antes del error. Después toInt lanza, y la ejecución salta directamente a catch. «Leí» se salta. Después de catch el programa sigue con «Fin».
La idea
Qué dice la excepción
La excepción es un objeto, y e en catch (e) es la variable que apunta a él. El mensaje está en e.message.
try { const tickets = toInt(await ask("")); console.log(`Total: ${tickets * 15} €`);} catch (e) { console.log(`Error: ${e.message}`);}12aError: "12a" no es un número entero.Aquí el mensaje es el que escribe toInt. Los errores del propio lenguaje vienen con sus propios mensajes en inglés, y su tipo está en e.name:
try { const scores = [62, 85, 47, 91, 78]; console.log(scores[5].toFixed(1));} catch (e) { console.log(e.name); console.log(e.message);}TypeErrorCannot read properties of undefined (reading 'toFixed')El mensaje te sirve a ti cuando buscas el error. Al cliente normalmente le muestras tu propio texto, en su idioma, como «Eso no es un número entero.»
La idea
Pregunta hasta que esté bien
El cliente se equivocó. No tienes que detenerte. Pregunta otra vez:
let tickets;while (true) { const text = await ask("¿Cuántas entradas? "); try { tickets = toInt(text); break; } catch (e) { console.log("Escribe un número entero, por ejemplo 3."); }}console.log(`Total: ${tickets * 15} €`);12adoce12¿Cuántas entradas? Escribe un número entero, por ejemplo 3.¿Cuántas entradas? Escribe un número entero, por ejemplo 3.¿Cuántas entradas? Total: 180 €while (true) sigue dando vueltas hasta que break lo detiene. Y a break solo se llega si toInt sale bien. Con un error, la ejecución salta a catch, y después el bucle vuelve a empezar y pregunta otra vez.
Más precisamente, para curiosos
Numbernunca lanza, así que en JavaScript la comprobación habitual es conif:Number.isInteger(Number(text)). Es justo lo que hacetoIntpor dentro. Una excepción es práctica cuando la comprobación está muy dentro de una función y el error tiene que llegar al lugar que sabe qué hacer.
Pruébalo
División entre cero
No toda excepción viene de la entrada. El valor medio de un pedido es la facturación dividida entre el número de pedidos. Pero el primer día no hay pedidos. ¿Qué pasa cuando divides entre cero?
¿Qué imprimirá el programa?
const revenue = 90;const orders = 0;const average = revenue / orders;console.log(average);Infinity. Y 0 / 0 da NaN. Incluso Math.trunc(90 / 0), la división entera de la lección sobre operadores, sigue siendo Infinity. El programa sigue con un valor incorrecto sin decir nada. Así que antes de una división, comprueba el divisor con if.Más precisamente, para curiosos
- En Java y C#, los números enteros no tienen ningún valor para el infinito, así que lo único que queda es la excepción. Para los números decimales, las reglas vienen del estándar IEEE 754 de números de coma flotante, que siguen todos los lenguajes.
- En JavaScript todos los números son
number, decimales según IEEE 754, así que la división nunca lanza una excepción.
Ejercicio
Programas que no se detienen
Resuélvelas en tu editor. No imprimas mensajes para pedir datos, para que tu salida coincida con el ejemplo.
1. Texto a número. Escribe una función toNumber(text) a partir de toInt: devuelve Number(text), y cuando el resultado es NaN (compruébalo con Number.isNaN) o el texto está vacío, lanza. Lee dos líneas. Para cada una, imprime el número o un mensaje de que no es un número. Atrapa la excepción donde llamas a la función.
123.45123.abcEl número es: 123.45Error: 123.abc no es un número.2. No hay tal producto. Los precios están en el array [120, 75, 300, 45, 89]. Lee un número de producto del 1 al 5 e imprime su precio. Si el número está fuera del array, dilo. Aquí no hay ninguna excepción: prices[6] es undefined, así que comprueba con if. Con la entrada 2 la salida es Precio: 75 €.
7No hay ningún producto con el número 7.3. Pedido medio. Lee la facturación y el número de pedidos (números enteros) e imprime la media con división entera. Si no hay pedidos, dilo. Aquí tampoco hay excepción (Infinity), así que comprueba con if. Con la entrada 900 y 4 la salida es Media: 225 €.
900Todavía no hay pedidos.Más tareas
- Edad, hasta que esté bien. Pregunta por una edad hasta que la entrada sea un número entero del 0 al 120. Para un texto como «veinte» di «Eso no es un número.», y para 150 di «La edad tiene que ser del 0 al 120.» Lo uno es una excepción, lo otro es un
if. - Suma de los válidos. Lee 5 líneas. Suma las que son números enteros y cuenta las que se saltan: con la entrada
10,abc,5,7.5,3la salida esSuma: 18, saltadas: 2. - División a petición. Lee dos números enteros e imprime el cociente. Con texto en lugar de un número y con división entre cero, el programa no se detiene. Vuelve a pedir los dos números. Aquí la división entre cero es un
if.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Parámetros y return · Recursión · Clases y objetos · Métodos de instancia
- Continúa en: try, catch y finally · Lanzar excepciones y crear las tuyas · Expresiones regulares · Archivos
- Ejercicios sobre el tema: Errores y excepciones