JavaScript
Tutorial JavaScript: Excepciones
Aprende a capturar y gestionar excepciones en JavaScript, incluyendo errores síncronos y asincrónicos con promesas y async/await.
Aprende JavaScript y certifícateFundamentos del manejo de excepciones: Captura y lanzamiento de errores en JavaScript
El manejo de excepciones es una técnica fundamental en la programación orientada a objetos que permite controlar situaciones inesperadas o erróneas durante la ejecución del código. En JavaScript, este mecanismo nos ayuda a identificar, capturar y responder a errores de forma estructurada, evitando que nuestra aplicación se detenga abruptamente.
Errores en JavaScript
JavaScript proporciona un sistema de errores basado en objetos que heredan de la clase Error
. Cuando ocurre un problema durante la ejecución, el intérprete genera (o "lanza") un objeto de error que contiene información sobre lo sucedido.
Los tipos de errores nativos más comunes son:
Error
: El tipo base para todos los erroresSyntaxError
: Errores de sintaxis en el códigoReferenceError
: Referencias a variables o funciones inexistentesTypeError
: Operaciones sobre tipos de datos incorrectosRangeError
: Valores fuera del rango permitido
Cada objeto de error contiene propiedades útiles:
try {
nonExistentFunction();
} catch (error) {
console.log(error.name); // "ReferenceError"
console.log(error.message); // "nonExistentFunction is not defined"
console.log(error.stack); // Traza de la pila de llamadas
}
Estructura try-catch-finally
El bloque try-catch-finally
es la estructura básica para manejar excepciones en JavaScript:
try {
// Código que podría generar un error
} catch (error) {
// Código que se ejecuta si ocurre un error
} finally {
// Código que se ejecuta siempre, haya error o no
}
Cada parte cumple una función específica:
- El bloque
try
contiene el código que queremos "proteger" y que podría generar errores. - El bloque
catch
se ejecuta solo si ocurre un error en el bloquetry
. - El bloque
finally
(opcional) se ejecuta siempre, independientemente de si hubo un error o no.
Un ejemplo práctico:
function divideNumbers(a, b) {
try {
if (b === 0) {
throw new Error("Cannot divide by zero");
}
const result = a / b;
return result;
} catch (error) {
console.error(`Error occurred: ${error.message}`);
return null;
} finally {
console.log("Division operation attempted");
}
}
console.log(divideNumbers(10, 2)); // 5
console.log(divideNumbers(10, 0)); // null (después de mostrar el error)
Lanzamiento de errores con throw
La palabra clave throw
permite generar errores manualmente cuando detectamos condiciones inválidas en nuestro código:
function validateAge(age) {
if (typeof age !== 'number') {
throw new TypeError("Age must be a number");
}
if (age < 0 || age > 120) {
throw new RangeError("Age must be between 0 and 120");
}
return true;
}
try {
validateAge("twenty"); // Lanzará TypeError
} catch (error) {
console.error(error.name + ": " + error.message);
}
Podemos lanzar cualquier tipo de error nativo o crear nuestros propios tipos personalizados utilizando la herencia de clases que ya conocemos:
// Creamos una clase de error personalizada extendiendo Error
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
function processUserData(user) {
if (!user.name) {
throw new ValidationError("User name is required");
}
// Continuar procesando...
}
Este es un excelente ejemplo de cómo el sistema de excepciones se integra con la programación orientada a objetos que hemos estudiado anteriormente.
Captura selectiva de errores
Podemos discriminar entre diferentes tipos de errores para manejarlos de forma específica utilizando instanceof
:
try {
// Código que podría generar diferentes tipos de errores
const data = JSON.parse(userInput);
processData(data);
} catch (error) {
if (error instanceof SyntaxError) {
console.error("Invalid JSON format");
} else if (error instanceof ReferenceError) {
console.error("Missing reference in the data");
} else {
console.error("An unexpected error occurred:", error);
}
}
Propagación de errores
A veces es mejor propagar un error hacia arriba en la pila de llamadas para que sea manejado por un nivel superior:
function validateData(data) {
if (!data.id) {
throw new Error("Missing ID field");
}
return true;
}
function processUserRecord(record) {
try {
validateData(record);
// Continuar procesando...
} catch (error) {
// Añadir contexto al error y propagarlo
throw new Error(`Error processing user record: ${error.message}`);
}
}
try {
processUserRecord({name: "John"});
} catch (error) {
console.error("Application error:", error.message);
// Mostrar mensaje amigable al usuario
}
Errores en funciones anidadas
Los errores se propagan automáticamente hacia arriba a través de las llamadas a funciones hasta encontrar un bloque catch
:
function innerFunction() {
throw new Error("Inner error");
}
function middleFunction() {
innerFunction(); // El error se propaga hacia arriba
}
function outerFunction() {
try {
middleFunction();
} catch (error) {
console.error("Caught in outer function:", error.message);
}
}
outerFunction(); // "Caught in outer function: Inner error"
Buenas prácticas básicas
- Sé específico: Captura solo los errores que puedes manejar adecuadamente.
- Proporciona mensajes claros: Los mensajes de error deben ser descriptivos y útiles.
- No abuses de try-catch: Úsalo para condiciones excepcionales, no para el flujo normal del programa.
- Considera el rendimiento: Los bloques try-catch tienen un pequeño impacto en el rendimiento.
// Enfoque recomendado
function getUserData(userId) {
if (!userId) {
throw new Error("User ID is required");
}
try {
return fetchUserFromDatabase(userId);
} catch (error) {
console.error(`Failed to fetch user ${userId}:`, error.message);
throw new Error(`Could not retrieve user data: ${error.message}`);
}
}
Diseño de estrategias robustas para el control de errores: Patrones y mejores prácticas
El manejo de excepciones es más que simplemente usar try-catch
. Al integrar estos mecanismos con los principios de POO que hemos aprendido, podemos crear estrategias más robustas para nuestras aplicaciones.
Jerarquía de errores personalizados
Aprovechando la herencia de clases que vimos en el módulo anterior, podemos crear una jerarquía de errores específicos para nuestra aplicación:
// Error base para toda la aplicación
class AppError extends Error {
constructor(message, statusCode = 500) {
super(message);
this.name = this.constructor.name;
this.statusCode = statusCode;
Error.captureStackTrace(this, this.constructor);
}
}
// Errores específicos por dominio
class ValidationError extends AppError {
constructor(message) {
super(message, 400);
this.validationErrors = [];
}
addValidationError(field, message) {
this.validationErrors.push({ field, message });
return this;
}
}
class AuthorizationError extends AppError {
constructor(message) {
super(message, 403);
}
}
Esta estructura de clases de error permite un manejo contextual más efectivo:
function processUserInput(data) {
try {
if (!data.username) {
const error = new ValidationError("Invalid user data")
.addValidationError("username", "Username is required");
throw error;
}
// Más validaciones...
} catch (error) {
if (error instanceof ValidationError) {
// Mostrar errores de validación en la interfaz
displayValidationErrors(error.validationErrors);
} else if (error instanceof AuthorizationError) {
// Redirigir al login
redirectToLogin();
} else {
// Manejar otros tipos de errores
showGenericError(error.message);
}
}
}
Patrón de envoltura de errores
La envoltura de errores preserva el contexto original mientras añade información valiosa en cada nivel:
function processPayment(paymentData) {
try {
validatePaymentData(paymentData);
chargeCustomer(paymentData);
sendReceipt(paymentData);
} catch (error) {
// Envolver el error con contexto adicional
const wrappedError = new AppError(
`Payment processing failed: ${error.message}`,
error.statusCode || 500
);
// Preservar el error original y añadir contexto
wrappedError.originalError = error;
wrappedError.paymentReference = paymentData.reference;
throw wrappedError;
}
}
Este patrón se alinea bien con el principio de encapsulación que estudiamos en POO, ya que oculta los detalles de implementación mientras proporciona información útil.
Recuperación elegante
La recuperación elegante permite que el sistema continúe funcionando incluso cuando ocurren errores:
function loadUserPreferences(userId) {
try {
return fetchUserPreferences(userId);
} catch (error) {
// Registrar el error
console.warn(`Failed to load preferences for user ${userId}:`, error);
// Proporcionar valores predeterminados como fallback
return getDefaultPreferences();
}
}
function getDefaultPreferences() {
return {
theme: 'light',
notifications: true,
language: 'en'
};
}
Validación preventiva
La validación temprana puede evitar errores antes de que ocurran, siguiendo el principio de "fallar rápido":
function transferFunds(fromAccount, toAccount, amount) {
// Validación preventiva
if (typeof amount !== 'number' || amount <= 0) {
throw new ValidationError("Amount must be a positive number");
}
if (!fromAccount || !toAccount) {
throw new ValidationError("Both accounts are required");
}
// Verificar fondos suficientes antes de intentar la transferencia
if (fromAccount.balance < amount) {
throw new AppError("Insufficient funds", 400);
}
// Proceder con la transferencia...
fromAccount.balance -= amount;
toAccount.balance += amount;
return {
success: true,
newBalance: fromAccount.balance
};
}
Contratos y precondiciones
Establecer contratos claros mediante precondiciones ayuda a detectar errores temprano:
function calculateDiscount(product, user) {
// Precondiciones
assert(product, "Product is required");
assert(user, "User is required");
assert(typeof product.price === 'number', "Product price must be a number");
// Lógica de negocio
let discount = 0;
if (user.isPremium) {
discount = product.price * 0.1;
}
if (product.isOnSale) {
discount += product.price * 0.05;
}
// Postcondición
assert(discount >= 0, "Discount cannot be negative");
return discount;
}
function assert(condition, message) {
if (!condition) {
throw new Error(`Assertion failed: ${message}`);
}
}
Manejo de errores en un contexto orientado a objetos
Podemos integrar el manejo de excepciones directamente en nuestras clases, respetando los principios de POO:
class BankAccount {
constructor(accountNumber, initialBalance = 0) {
this.accountNumber = accountNumber;
this.balance = initialBalance;
this.transactions = [];
}
deposit(amount) {
try {
if (amount <= 0) {
throw new ValidationError("Deposit amount must be positive");
}
this.balance += amount;
this.recordTransaction("deposit", amount);
return this.balance;
} catch (error) {
console.error(`Error in account ${this.accountNumber}:`, error);
throw error; // Re-lanzar para manejo superior
}
}
withdraw(amount) {
try {
if (amount <= 0) {
throw new ValidationError("Withdrawal amount must be positive");
}
if (amount > this.balance) {
throw new ValidationError("Insufficient funds");
}
this.balance -= amount;
this.recordTransaction("withdrawal", amount);
return this.balance;
} catch (error) {
console.error(`Error in account ${this.accountNumber}:`, error);
throw error;
}
}
recordTransaction(type, amount) {
this.transactions.push({
type,
amount,
date: new Date(),
balance: this.balance
});
}
}
// Uso con manejo de excepciones
try {
const account = new BankAccount("123456", 1000);
account.deposit(500);
account.withdraw(2000); // Esto generará un error
} catch (error) {
if (error instanceof ValidationError) {
console.log("Validation error:", error.message);
} else {
console.error("Unexpected error:", error);
}
}
Manejo de excepciones en contextos asíncronos: Promesas, async/await y entornos distribuidos
El código asíncrono plantea desafíos adicionales para el manejo de excepciones. Si bien este tema se explorará en profundidad en el módulo de programación asíncrona, es importante entender los conceptos básicos y cómo se relacionan con lo que hemos aprendido hasta ahora.
Errores en promesas
En JavaScript moderno, las operaciones asíncronas a menudo utilizan promesas, que tienen su propio mecanismo para manejar errores mediante el método .catch()
:
fetchUserData(userId)
.then(user => {
console.log('User data:', user);
})
.catch(error => {
console.error('Failed to fetch user data:', error.message);
});
Un aspecto importante de las promesas es que los errores se propagan automáticamente a través de la cadena de .then()
hasta encontrar un .catch()
:
fetchUserData(userId)
.then(user => processUser(user)) // Si fetchUserData falla, este paso se omite
.then(result => saveToDatabase(result)) // Este también se omite
.catch(error => {
// Captura errores de cualquier paso anterior
console.error('Operation failed:', error);
});
Introducción a async/await con excepciones
La sintaxis async/await
simplifica el manejo de errores asíncronos permitiendo usar bloques try-catch
tradicionales:
async function getUserData(userId) {
try {
const response = await fetchData(`/users/${userId}`);
return response;
} catch (error) {
console.error(`Failed to fetch user ${userId}:`, error);
throw new Error(`Could not retrieve user data: ${error.message}`);
}
}
Es importante recordar que una función async
siempre devuelve una promesa, por lo que los errores deben manejarse adecuadamente cuando se llama a estas funciones:
// Opción 1: Usando .catch()
getUserData(123)
.then(user => displayUserProfile(user))
.catch(error => showErrorMessage(error.message));
// Opción 2: Usando async/await con try-catch
async function displayUser(userId) {
try {
const user = await getUserData(userId);
displayUserProfile(user);
} catch (error) {
showErrorMessage(error.message);
}
}
Errores comunes en código asíncrono
Uno de los errores más frecuentes es olvidar manejar las promesas rechazadas:
// Incorrecto: error no manejado
function fetchAndProcess(url) {
fetch(url).then(response => response.json());
// Si la promesa se rechaza, el error no se maneja
}
// Correcto: con manejo de errores
function fetchAndProcess(url) {
fetch(url)
.then(response => response.json())
.catch(error => console.error("Failed to fetch:", error));
}
Integración de excepciones síncronas y asíncronas
Un desafío común es integrar manejo de errores en código que mezcla operaciones síncronas y asíncronas:
class UserService {
constructor(apiClient) {
this.apiClient = apiClient;
this.cache = new Map();
}
async getUser(userId) {
// Verificación síncrona
if (!userId) {
throw new ValidationError("User ID is required");
}
// Verificar caché (síncrono)
if (this.cache.has(userId)) {
return this.cache.get(userId);
}
// Operación asíncrona
try {
const user = await this.apiClient.fetchUser(userId);
// Más validaciones síncronas
if (!user.name) {
throw new ValidationError("Invalid user data received");
}
// Guardar en caché
this.cache.set(userId, user);
return user;
} catch (error) {
// Transformar errores específicos de la API
if (error.status === 404) {
throw new NotFoundError(`User ${userId} not found`);
}
// Re-lanzar otros errores
throw error;
}
}
}
Un vistazo a patrones asíncronos más avanzados
En el módulo de programación asíncrona exploraremos estos conceptos con mayor profundidad, incluyendo:
- Manejo de múltiples operaciones asíncronas con
Promise.all()
yPromise.allSettled()
- Implementación de timeouts para operaciones asíncronas
- Estrategias de reintentos para operaciones que pueden fallar temporalmente
- Manejo de errores en APIs basadas en eventos
- Patrones de recuperación en sistemas distribuidos
Por ahora, es importante entender que los principios fundamentales de manejo de excepciones se aplican tanto en código síncrono como asíncrono, aunque con algunas consideraciones adicionales.
Otros ejercicios de programación de JavaScript
Evalúa tus conocimientos de esta lección Excepciones con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Clases y objetos
Uso de operadores
Uso de operadores
Estructuras de control
Proyecto Manipulación DOM
Excepciones
Transformación con map()
Arrays y Métodos
Reto Métodos de Strings
Transformación con map()
Funciones flecha
Async / Await
Selección de elementos DOM
API Fetch
Encapsulación
Mapas con Map
Creación y uso de variables
Polimorfismo
Reto Funciones flecha
Tipos de datos
Reto Operadores avanzados
Promises
Reto Estructuras de control
Estructuras de control
Pruebas unitarias
Inmutabilidad y programación funcional pura
Funciones flecha
Polimorfismo
Reto Polimorfismo
Array
Transformación con map()
Reto Variables
Gestor de tareas con JavaScript
Proyecto Modificación de elementos DOM
Manipulación DOM
Funciones
Conjuntos con Set
Reto Prototipos y cadena de prototipos
Reto Encapsulación
Funciones flecha
Async / Await
Reto Excepciones
Reto Filtrado con filter() y find()
Creación y uso de variables
Excepciones
Promises
Funciones cierre (closure)
Reto Herencia
Herencia
Proyecto Eventos del DOM
Herencia
Selección de elementos DOM
Modificación de elementos DOM
Reto Clases y objetos
Filtrado con filter() y find()
Funciones cierre (closure)
Reto Destructuring de objetos y arrays
Callbacks
Funciones
Mapas con Map
Reducción con reduce()
Callbacks
Manipulación DOM
Introducción al DOM
Reto Funciones
Reto Funciones cierre (closure)
Promises
Reto Reducción con reduce()
Async / Await
Reto Estructuras de control
Eventos del DOM
Introducción a JavaScript
Async / Await
Promises
Selección de elementos DOM
Filtrado con filter() y find()
Callbacks
Creación de clases y objetos Restaurante
Reducción con reduce()
Filtrado con filter() y find()
Reducción con reduce()
Conjuntos con Set
Herencia de clases
Eventos del DOM
Clases y objetos
Modificación de elementos DOM
Mapas con Map
Proyecto carrito compra agoodshop
Introducción a JavaScript
Reto Mapas con Map
Funciones
Proyecto administrador de contactos
Reto Expresiones regulares
Tipos de datos
Clases y objetos
Array
Conjuntos con Set
Array
Encapsulación
Todas las lecciones de JavaScript
Accede a todas las lecciones de JavaScript y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Javascript
Introducción Y Entorno
Tipos De Datos
Sintaxis
Variables
Sintaxis
Operadores
Sintaxis
Estructuras De Control
Sintaxis
Funciones
Sintaxis
Funciones Cierre (Closure)
Sintaxis
Métodos De Strings
Sintaxis
Funciones Cierre (Closure)
Sintaxis
Operadores Avanzados
Sintaxis
Funciones
Sintaxis
Expresiones Regulares
Sintaxis
Estructuras De Control
Sintaxis
Arrays Y Métodos
Estructuras De Datos
Conjuntos Con Set
Estructuras De Datos
Mapas Con Map
Estructuras De Datos
Conjuntos Con Set
Estructuras De Datos
Funciones Flecha
Programación Funcional
Filtrado Con Filter() Y Find()
Programación Funcional
Transformación Con Map()
Programación Funcional
Reducción Con Reduce()
Programación Funcional
Funciones Flecha
Programación Funcional
Transformación Con Map()
Programación Funcional
Inmutabilidad Y Programación Funcional Pura
Programación Funcional
Clases Y Objetos
Programación Orientada A Objetos
Excepciones
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
This Y Contexto
Programación Orientada A Objetos
Patrón De Módulos Y Namespace
Programación Orientada A Objetos
Prototipos Y Cadena De Prototipos
Programación Orientada A Objetos
Destructuring De Objetos Y Arrays
Programación Orientada A Objetos
Manipulación Dom
Dom
Selección De Elementos Dom
Dom
Modificación De Elementos Dom
Dom
Eventos Del Dom
Dom
Localstorage Y Sessionstorage
Dom
Bom (Browser Object Model)
Dom
Callbacks
Programación Asíncrona
Promises
Programación Asíncrona
Async / Await
Programación Asíncrona
Promises
Programación Asíncrona
Api Fetch
Programación Asíncrona
Async / Await
Programación Asíncrona
Naturaleza De Js Y Event Loop
Programación Asíncrona
Callbacks
Programación Asíncrona
Websockets
Programación Asíncrona
Módulos En Es6
Construcción
Configuración De Bundlers Como Vite
Construcción
Eslint Y Calidad De Código
Construcción
Npm Y Dependencias
Construcción
Introducción A Pruebas En Js
Testing
Pruebas Unitarias
Testing
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender qué son las excepciones y por qué son importantes en JavaScript.
- Conocer la estructura
try-catch-finally
y cómo se utiliza para manejar excepciones. - Entender la función de cada bloque (
try
,catch
,finally
) en el manejo de excepciones. - Aprender a lanzar y capturar excepciones usando la palabra clave
throw
. - Saber cómo utilizar múltiples bloques
catch
para manejar diferentes tipos de excepciones. - Comprender la utilidad del bloque
finally
para ejecutar código que debe ejecutarse siempre, independientemente de si ocurrió una excepción o no. - Aprender a personalizar los mensajes de error en las excepciones para brindar información más específica.
- Entender cómo el manejo adecuado de excepciones mejora la robustez y la legibilidad del código.