Aprende leyendo en orden

Lanzar excepciones — throw y errores personalizados

Lanza excepciones con throw new Error, crea tus propios tipos de error con extends Error y conserva el error original con cause.

Aunque la página de pedidos envíe una cantidad de 0, la función que calcula el subtotal hace la cuenta igualmente, y se acepta un pedido de 0 yenes. Mostrar un aviso tampoco detiene a quien llama: no se entera del problema y sigue adelante hasta guardar el pedido.

En este artículo verás throw new Error, que te permite lanzar una excepción tú mismo, y los errores personalizados, que son tipos de error que defines tú.

Detenerse ante un valor incorrecto — throw new Error

Imagina que no quieres que se calcule el subtotal si la cantidad es un decimal o un número negativo. Avisar con return null no basta: si quien llama se olvida de comprobarlo, sumar el envío con null + 500 da 500 y el cálculo sigue adelante.

Un objeto Error (un valor con name y message, pensado para lanzarse como excepción) se crea con new Error("mensaje"). Si se lo pasas a throw, la excepción se lanza en ese mismo punto; si la llamada se hizo dentro de try, ese valor llega como el error de catch.

function calcSubtotal(unitPrice, quantity) {
  // Si no es un entero mayor o igual que 1, lanza una excepción aquí mismo
  if (!Number.isInteger(quantity) || quantity < 1) {
    // new Error por sí solo no detiene nada. Pásalo a throw para convertirlo en excepción
    throw new Error(`La cantidad debe ser un entero mayor o igual que 1: ${quantity}`);
  }
  return unitPrice * quantity;
}

try {
  console.log(calcSubtotal(1200, 3));   // 3600
  console.log(calcSubtotal(1200, 0));   // lanza una excepción, así que no se muestra nada
  console.log("Pedido confirmado");     // esta línea tampoco se ejecuta
} catch (error) {
  console.log(error.name);              // Error
  console.log(error.message);           // La cantidad debe ser un entero mayor o igual que 1: 0
}
El código se detiene solo si se lo pasas a throw
Solo new Errordentro del ifSe crea unobjeto ErrorNo pasa nada ysigue la ejecución1200 * 0devuelve 0throw new Errordentro del ifSe crea unobjeto ErrorSe lanza unaexcepción ahíEl valor llega alerror de catch
Las dos versiones crean un objeto Error. Solo cuando se pasa a throw se detiene el código en ese punto.

Pon el if de validación al principio de la función, antes del cálculo. throw sale de la función en ese mismo punto, igual que return, así que el return unitPrice * quantity; que viene después no se ejecuta y quien llama nunca recibe un 0.

Comprueba la valoración de satisfacción antes de guardar una encuesta. scores ya está declarado.

① Si la valoración no es un entero, lanza una excepción con el mensaje "La valoración debe ser un número entero".

② Si está fuera del rango de 1 a 5, lanza una excepción con el mensaje "La valoración debe estar entre 1 y 5".

③ Si no hay ningún problema, devuélvela con la forma "Respuesta enviada con una valoración de 4".

④ Pasa uno por uno los valores de scores y muestra el resultado o el mensaje de error, sin detenerte aunque alguno falle.

(Si se ejecuta correctamente, aparecerá una explicación.)

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

Crear un tipo para los errores de entrada — extends Error

Una excepción lanzada con new Error siempre tiene Error como name, sea cual sea la validación. Un código postal con un número de dígitos incorrecto y un artículo sin existencias llegan con el mismo nombre, así que el bloque catch no puede saber si se trata de un fallo que el usuario puede arreglar corrigiendo lo que escribió.

Si añades extends Error a una class (la sintaxis que define un tipo de objeto que se crea con new), creas un tipo que hereda el comportamiento de Error. Si escribes name = "ValidationError"; dentro de las llaves, las excepciones creadas a partir de este tipo tendrán esa cadena como name.

// Hereda de Error para crear un tipo para los errores de entrada
class ValidationError extends Error {
  name = "ValidationError";   // pasa a ser el name de las excepciones creadas con new
}

function checkZipCode(zipCode) {
  if (zipCode.length !== 5) {
    throw new ValidationError(`El código postal debe tener 5 dígitos: ${zipCode}`);
  }
  return zipCode;
}

try {
  checkZipCode("9021");
} catch (error) {
  console.log(error.name);               // ValidationError
  console.log(error.message);            // El código postal debe tener 5 dígitos: 9021
  console.log(error instanceof Error);   // true
}
Dónde encajan las clases de extends Error
Error — tipos de excepción que tienen message y name
  • Tanto para una excepción creada con new Error("mensaje") como para cualquier tipo de este recuadro, instanceof Error es true
Tipos integrados — vienen incluidos en JavaScript
  • SyntaxError / TypeError / RangeError — los tipos del artículo anterior
Tipos creados con extends Error
  • ValidationError — representa un error de entrada
  • name es el "ValidationError" escrito dentro de la clase
  • instanceof ValidationError es true solo dentro de este recuadro
Los dos están dentro del recuadro de Error, y ninguno está dentro del otro. instanceof ValidationError es true solo dentro de su propio recuadro.

También puedes pasar una cadena a throw, pero una cadena no tiene ni name ni message, así que leer error.message en catch da undefined. La tabla de abajo muestra qué puedes leer en catch según el valor que se pase a throw.

Valor pasado a throwname que se lee en catchinstanceof Error
"Código postal no válido" (cadena)undefined (una cadena no lo tiene)false
new Error("Código postal no válido")Errortrue
ValidationError con nameValidationErrortrue
ValidationError sin nameError (el heredado)true

Muestra por qué se rechaza un archivo. QuotaError, files y checkFile ya están declarados.

① Define un FormatError que herede de Error y asígnale un name.

② En checkFile, si la extensión no es pdf, lanza un FormatError con el mensaje "Solo se aceptan archivos PDF".

③ Revisa uno por uno los archivos de files y muestra el valor devuelto o "name: message".

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

Comprobar primero tus propios tipos — el orden de instanceof

Un error de entrada lo puede arreglar el usuario, pero una excepción inesperada como un TypeError no desaparece por mucho que cambie lo que escribió. Quieres que catch muestre dos mensajes distintos, pero si escribes las comprobaciones en el orden equivocado, hasta los errores de entrada reciben el mensaje pensado para las excepciones inesperadas.

if y else if se comprueban de arriba abajo, y solo se ejecuta la primera rama que coincide. Un ValidationError también da true con instanceof Error, así que si instanceof Error va primero, un ValidationError también acaba en esa rama.

class ValidationError extends Error {
  name = "ValidationError";
}
const error = new ValidationError("El código postal debe tener 5 dígitos");

// Si compruebas Error primero, un ValidationError también cae en esta rama
if (error instanceof Error) {
  console.log("No se pudo guardar");            // No se pudo guardar
} else if (error instanceof ValidationError) {
  console.log("Revisa los datos ingresados");   // aquí no llega
}

// Comprueba primero la subclase
if (error instanceof ValidationError) {
  console.log("Revisa los datos ingresados");   // Revisa los datos ingresados
} else if (error instanceof Error) {
  console.log("No se pudo guardar");
}
El orden de las comprobaciones decide la rama
instanceof Errorva primerotrue también paraValidationErrorLas siguientes nose prueban"No se pudoguardar"ValidationErrorva primerotrue: se creó conValidationErrorLa de Errorno se prueba"Revisa los datosingresados"
En la fila de arriba, la primera comprobación da true, así que la segunda nunca se prueba. Comprueba los tipos que heredan de Error antes que el propio Error.

Los tipos integrados como TypeError también heredan de Error, así que la rama instanceof Error puesta al final los recoge a todos. Usa esa rama para mostrar otro mensaje en las excepciones que no se arreglan corrigiendo los datos ingresados.

Procesa solicitudes de cambio de correo electrónico. ValidationError, changeEmail y requests ya están declarados.

① Pasa cada elemento de requests a changeEmail y muestra el resultado.

② Si es un ValidationError, muestra "Revisa tu correo electrónico: " seguido de su mensaje.

③ Si es cualquier otro Error, muestra "No se pudo cambiar: " seguido del nombre del tipo.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

Lanzar con la excepción original adjunta — cause y excepciones no capturadas

Imagina que se lanza un SyntaxError en una función que lee la respuesta de un servicio de pagos. Si te limitas a relanzarlo, su tipo no le dice a quien llama que el fallo ocurrió al procesar un pago. Pero si lo cambias por una excepción de un tipo nuevo, pierdes el dato de que originalmente era un SyntaxError.

cause (una opción que adjunta la excepción original a una nueva) va en el segundo argumento, como en new Error("mensaje", { cause: error }). Funciona igual con los tipos creados con extends Error, y quien capture la nueva excepción puede leer la original desde error.cause.

class PaymentError extends Error {
  name = "PaymentError";
}

function readPayment(text) {
  try {
    return JSON.parse(text);
  } catch (error) {
    // Adjunta la excepción original como cause y lánzala como error de pago
    throw new PaymentError("No se puede leer el resultado del pago", { cause: error });
  }
}

try {
  readPayment('{"orderId":"P-3107","amount":}');
} catch (error) {
  console.log(error.name);          // PaymentError
  console.log(error.message);       // No se puede leer el resultado del pago
  console.log(error.cause.name);    // SyntaxError
}
La excepción original queda en cause
error — el PaymentError que lanzó readPayment
  • error.name es PaymentError
  • error.message es No se puede leer el resultado del pago
error.cause — la excepción original que lanzó JSON.parse
  • error.cause.name es SyntaxError
  • error.cause.message es la descripción de JSON.parse, sin cambios
La excepción externa es un PaymentError, y cause contiene el SyntaxError original. La excepción original se conserva intacta en error.cause.

Si la cambias por una excepción nueva sin cause, error.cause es undefined y no puedes leer ni el tipo ni la descripción originales. Con cause, quien llama puede usar error.name para mostrar un mensaje de fallo en el pago y, aun así, registrar lo que hay en error.cause.

Una excepción que nadie captura detiene la ejecución

Si ningún catch recibe una excepción lanzada, esta se convierte en una excepción no capturada (una excepción que ningún catch gestiona), y la ejecución se detiene ahí mismo; ninguna de las líneas restantes se ejecuta. Si quitas el try externo del código de arriba, se muestra un error con el nombre del tipo y el mensaje, como PaymentError: No se puede leer el resultado del pago.

Importa registros de asistencia. ImportError y records ya están declarados.

① En importRecords, toma los cinco primeros caracteres de cada hora de entrada y muéstralos con la forma "E-104: 09:02".

② Si falla, lanza un ImportError con el mensaje "Importación detenida en la fila de E-105", adjuntando la excepción original.

③ Llama a importRecords y, si falla, muestra el mensaje y después "Excepción original: " seguido del nombre del tipo de la excepción original.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado
QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1¿Cuál es el name de una excepción creada a partir de class ShippingError extends Error {}?

Pregunta 2Si compruebas instanceof Error primero, ¿en qué rama entra una excepción ValidationError?

Pregunta 3Capturaste con catch (error) una excepción lanzada con cause. ¿Cómo lees el name de la excepción original?