JavaScript

JavaScript

Tutorial JavaScript: Excepciones

JavaScript excepciones: manejo y prevención. Domina el manejo de excepciones en JavaScript con técnicas y ejemplos prácticos.

Aprende JavaScript y certifícate

Fundamentos del manejo de excepciones: Captura y lanzamiento de errores en JavaScript

El manejo de excepciones es una técnica fundamental en la programación que permite controlar situaciones inesperadas o erróneas durante la ejecución de nuestro 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 errores
  • SyntaxError: Errores de sintaxis en el código
  • ReferenceError: Referencias a variables o funciones inexistentes
  • TypeError: Operaciones sobre tipos de datos incorrectos
  • RangeError: 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 bloque try.
  • 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:

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

Captura selectiva de errores

Podemos discriminar entre diferentes tipos de errores para manejarlos de forma específica:

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}`);
  }
}

El manejo adecuado de excepciones mejora la robustez de nuestras aplicaciones y proporciona una mejor experiencia tanto para los desarrolladores como para los usuarios finales, permitiéndonos anticipar y responder a situaciones problemáticas de manera controlada.

Diseño de estrategias robustas para el control de errores: Patrones y mejores prácticas

El manejo de excepciones va más allá de simplemente usar bloques try-catch. Un enfoque estratégico al control de errores puede mejorar significativamente la robustez, mantenibilidad y experiencia de usuario de nuestras aplicaciones JavaScript. Vamos a explorar patrones y prácticas que nos ayudarán a diseñar sistemas más resilientes.

Jerarquía de errores personalizados

Crear una jerarquía de errores bien estructurada permite categorizar problemas y facilitar su manejo específico:

// 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 permite un manejo contextual de errores:

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 {
      // Manejar otros tipos de errores
      logErrorAndNotify(error);
    }
  }
}

Patrón de fábrica de errores

El patrón de fábrica centraliza la creación de errores, garantizando consistencia en toda la aplicación:

// Fábrica de errores
const ErrorFactory = {
  createNotFoundError(resource, id) {
    const error = new AppError(`${resource} with id ${id} not found`, 404);
    error.resource = resource;
    error.resourceId = id;
    return error;
  },
  
  createValidationError(fieldErrors) {
    const error = new ValidationError("Validation failed");
    fieldErrors.forEach(({ field, message }) => {
      error.addValidationError(field, message);
    });
    return error;
  },
  
  createNetworkError(endpoint, originalError) {
    const error = new AppError(`Failed to connect to ${endpoint}`, 503);
    error.originalError = originalError;
    return error;
  }
};

// Uso
function fetchUser(userId) {
  if (!userExists(userId)) {
    throw ErrorFactory.createNotFoundError("User", userId);
  }
  // Continuar con la lógica...
}

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;
  }
}

Centralización del manejo de errores

Un manejador centralizado proporciona consistencia y evita la duplicación de código:

// Manejador central de errores
const ErrorHandler = {
  handle(error, context = {}) {
    // Registrar el error
    this.logError(error, context);
    
    // Determinar la respuesta apropiada
    if (error instanceof ValidationError) {
      return this.handleValidationError(error);
    } else if (error instanceof AuthorizationError) {
      return this.handleAuthError(error);
    } else {
      return this.handleGenericError(error);
    }
  },
  
  logError(error, context) {
    console.error({
      message: error.message,
      name: error.name,
      stack: error.stack,
      context,
      timestamp: new Date().toISOString()
    });
  },
  
  handleValidationError(error) {
    return {
      success: false,
      status: error.statusCode,
      errors: error.validationErrors,
      message: "Validation failed"
    };
  },
  
  // Otros manejadores específicos...
  
  handleGenericError(error) {
    // Ocultar detalles técnicos en producción
    const isProduction = process.env.NODE_ENV === 'production';
    return {
      success: false,
      status: error.statusCode || 500,
      message: isProduction ? "An unexpected error occurred" : error.message
    };
  }
};

// Uso en una API
app.post('/api/users', (req, res) => {
  try {
    const user = createUser(req.body);
    res.json({ success: true, user });
  } catch (error) {
    const response = ErrorHandler.handle(error, { 
      endpoint: '/api/users', 
      method: 'POST' 
    });
    res.status(response.status).json(response);
  }
});

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:

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

Monitoreo y análisis de errores

Implementar un sistema de monitoreo de errores permite identificar patrones y problemas recurrentes:

class ErrorMonitor {
  constructor() {
    this.errorCounts = new Map();
    this.errorSamples = new Map();
  }
  
  trackError(error) {
    const errorType = error.name;
    
    // Incrementar contador
    const currentCount = this.errorCounts.get(errorType) || 0;
    this.errorCounts.set(errorType, currentCount + 1);
    
    // Guardar muestra del error
    if (!this.errorSamples.has(errorType)) {
      this.errorSamples.set(errorType, {
        message: error.message,
        stack: error.stack,
        firstSeen: new Date(),
        lastSeen: new Date()
      });
    } else {
      const sample = this.errorSamples.get(errorType);
      sample.lastSeen = new Date();
      sample.occurrences = (sample.occurrences || 1) + 1;
    }
    
    // Alertar si un error ocurre con frecuencia
    if (currentCount + 1 >= 10) {
      this.triggerAlert(errorType);
    }
  }
  
  getErrorReport() {
    return {
      summary: Object.fromEntries(this.errorCounts),
      samples: Object.fromEntries(this.errorSamples)
    };
  }
  
  triggerAlert(errorType) {
    console.error(`ALERT: High frequency of ${errorType} errors detected!`);
    // Enviar notificación al equipo de desarrollo
  }
}

// Uso global
const monitor = new ErrorMonitor();

// Integración con el manejador de errores
const originalHandleMethod = ErrorHandler.handle;
ErrorHandler.handle = function(error, context) {
  monitor.trackError(error);
  return originalHandleMethod.call(this, error, context);
};

Estrategias de reintentos

Para operaciones que pueden fallar temporalmente, como peticiones de red, una estrategia de reintentos puede aumentar la resiliencia:

async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  let lastError;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await fetch(url, options);
    } catch (error) {
      lastError = error;
      
      // Solo reintentar en errores de red, no en errores de servidor
      if (!isNetworkError(error)) {
        throw error;
      }
      
      console.warn(`Attempt ${attempt} failed, retrying...`, error.message);
      
      // Esperar con backoff exponencial antes de reintentar
      const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  // Si llegamos aquí, todos los intentos fallaron
  throw new AppError(`Failed after ${maxRetries} attempts: ${lastError.message}`, 503);
}

function isNetworkError(error) {
  return error.message.includes('network') || 
         error.message.includes('connection') ||
         error.message.includes('timeout');
}

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}`);
  }
}

Estas estrategias y patrones, aplicados de manera consistente, crean un sistema de manejo de errores que no solo responde a problemas, sino que los anticipa y minimiza su impacto, resultando en aplicaciones más robustas y una mejor experiencia para los usuarios.

Manejo de excepciones en contextos asíncronos: Promesas, async/await y entornos distribuidos

El manejo de errores en código asíncrono presenta desafíos únicos que van más allá de los bloques try-catch tradicionales. En JavaScript moderno, donde las operaciones asíncronas son fundamentales, necesitamos estrategias específicas para capturar y gestionar excepciones en promesas, funciones async/await y entornos distribuidos.

Errores en promesas

Las promesas en JavaScript tienen su propio mecanismo para manejar errores a través del método .catch():

fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => console.log('Data received:', data))
  .catch(error => {
    console.error('Fetch operation failed:', error.message);
  });

Un aspecto crucial 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);
  });

Promesas rechazadas no capturadas

Uno de los problemas más comunes en aplicaciones JavaScript es no capturar rechazos de promesas, lo que puede causar fugas de memoria y comportamientos inesperados. Podemos detectar estos casos con:

window.addEventListener('unhandledrejection', event => {
  console.error('Unhandled promise rejection:', event.reason);
  // Registrar el error o notificar al usuario
  event.preventDefault(); // Evita que el error aparezca en la consola
});

En Node.js:

process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise);
  console.error('Reason:', reason);
  // Posiblemente: process.exit(1);
});

Manejo de errores con async/await

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 fetch(`https://api.example.com/users/${userId}`);
    
    if (!response.ok) {
      throw new Error(`API error: ${response.status}`);
    }
    
    const userData = await response.json();
    return userData;
  } catch (error) {
    console.error(`Failed to fetch user ${userId}:`, error);
    throw new Error(`Could not retrieve user data: ${error.message}`);
  }
}

Sin embargo, 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);
  }
}

Patrones para operaciones asíncronas paralelas

Cuando ejecutamos múltiples operaciones asíncronas en paralelo, tenemos diferentes opciones para manejar errores:

  • Promise.all(): Falla rápido si cualquier promesa es rechazada
async function loadDashboardData(userId) {
  try {
    const [profile, posts, notifications] = await Promise.all([
      fetchUserProfile(userId),
      fetchUserPosts(userId),
      fetchNotifications(userId)
    ]);
    
    return { profile, posts, notifications };
  } catch (error) {
    // Si cualquier operación falla, entramos aquí
    console.error('Dashboard data loading failed:', error);
    throw error;
  }
}
  • Promise.allSettled(): Espera a que todas las promesas se completen, independientemente del resultado
async function loadDashboardData(userId) {
  const results = await Promise.allSettled([
    fetchUserProfile(userId),
    fetchUserPosts(userId),
    fetchNotifications(userId)
  ]);
  
  // Procesar resultados individualmente
  const dashboard = {};
  
  if (results[0].status === 'fulfilled') {
    dashboard.profile = results[0].value;
  } else {
    console.error('Profile loading failed:', results[0].reason);
    dashboard.profile = getDefaultProfile();
  }
  
  // Procesar el resto de resultados...
  
  return dashboard;
}

Timeouts en operaciones asíncronas

Para evitar que las operaciones asíncronas se bloqueen indefinidamente, podemos implementar timeouts:

function fetchWithTimeout(url, options = {}, timeout = 5000) {
  return Promise.race([
    fetch(url, options),
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('Request timeout')), timeout)
    )
  ]);
}

// Uso
async function getApiData() {
  try {
    const response = await fetchWithTimeout('https://api.example.com/data', {}, 3000);
    return response.json();
  } catch (error) {
    if (error.message === 'Request timeout') {
      // Manejar específicamente el timeout
      console.error('The request took too long to complete');
    } else {
      // Manejar otros errores
      console.error('API request failed:', error);
    }
    return null;
  }
}

Manejo de errores en operaciones de red

Las aplicaciones modernas a menudo dependen de servicios externos, lo que introduce puntos de fallo adicionales. Una estrategia robusta incluye:

async function fetchWithErrorHandling(url) {
  try {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 5000);
    
    const response = await fetch(url, { 
      signal: controller.signal,
      headers: { 'Content-Type': 'application/json' }
    });
    
    clearTimeout(timeoutId);
    
    if (!response.ok) {
      // Manejar errores HTTP
      if (response.status === 404) {
        throw new NotFoundError(`Resource not found: ${url}`);
      } else if (response.status === 401 || response.status === 403) {
        throw new AuthError('Authentication required');
      } else {
        throw new ApiError(`Server responded with ${response.status}`);
      }
    }
    
    return await response.json();
  } catch (error) {
    // Categorizar errores de red
    if (error.name === 'AbortError') {
      throw new TimeoutError('Request timed out');
    } else if (error instanceof TypeError && error.message.includes('network')) {
      throw new NetworkError('Network connection failed');
    }
    
    // Propagar errores ya categorizados
    throw error;
  }
}

Manejo de errores en eventos asíncronos

Para APIs basadas en eventos como WebSockets, necesitamos un enfoque diferente:

function setupWebSocketWithErrorHandling(url) {
  const socket = new WebSocket(url);
  
  // Manejar errores de conexión
  socket.addEventListener('error', event => {
    console.error('WebSocket error:', event);
    notifyUser('Connection error occurred');
  });
  
  // Manejar desconexiones
  socket.addEventListener('close', event => {
    if (!event.wasClean) {
      console.warn(`Connection closed abnormally, code: ${event.code}`);
      
      // Implementar reconexión con backoff exponencial
      reconnectWithBackoff(url);
    }
  });
  
  return socket;
}

// Implementación de reconexión con backoff exponencial
let reconnectAttempt = 0;
function reconnectWithBackoff(url) {
  const maxReconnectAttempts = 5;
  
  if (reconnectAttempt >= maxReconnectAttempts) {
    console.error('Maximum reconnection attempts reached');
    notifyUser('Unable to establish connection');
    return;
  }
  
  const delay = Math.min(1000 * Math.pow(2, reconnectAttempt), 30000);
  console.log(`Attempting to reconnect in ${delay}ms...`);
  
  setTimeout(() => {
    reconnectAttempt++;
    setupWebSocketWithErrorHandling(url);
  }, delay);
}

Manejo de errores en microfrontends y arquitecturas distribuidas

En aplicaciones distribuidas, los errores pueden ocurrir en diferentes contextos y fronteras:

// Comunicación entre microfrontends
window.addEventListener('error', event => {
  // Evitar bucles infinitos verificando el origen
  if (event.error && event.error.fromMicroFrontend) {
    return;
  }
  
  // Notificar al sistema principal
  window.parent.postMessage({
    type: 'ERROR',
    payload: {
      message: event.error?.message || 'Unknown error',
      source: 'userProfileMicrofrontend',
      timestamp: Date.now(),
      fromMicroFrontend: true
    }
  }, '*');
});

// En la aplicación principal
window.addEventListener('message', event => {
  if (event.data.type === 'ERROR') {
    const { source, message } = event.data.payload;
    console.error(`Error in ${source}:`, message);
    
    // Centralizar el registro de errores
    errorMonitoringService.captureError({
      context: 'microfrontend',
      source,
      message,
      level: 'error'
    });
  }
});

Estrategias de circuit breaker para APIs externas

El patrón circuit breaker previene fallos en cascada cuando un servicio externo está experimentando problemas:

class CircuitBreaker {
  constructor(request, options = {}) {
    this.request = request;
    this.state = 'CLOSED';
    this.failureCount = 0;
    this.successCount = 0;
    this.lastFailureTime = null;
    this.failureThreshold = options.failureThreshold || 3;
    this.resetTimeout = options.resetTimeout || 30000;
    this.monitorInterval = options.monitorInterval || 5000;
  }
  
  async execute(...args) {
    if (this.state === 'OPEN') {
      // Verificar si debemos intentar restablecer el circuito
      if (Date.now() - this.lastFailureTime > this.resetTimeout) {
        this.state = 'HALF_OPEN';
        console.log('Circuit breaker state: HALF_OPEN');
      } else {
        throw new Error('Circuit is OPEN - request rejected');
      }
    }
    
    try {
      const result = await this.request(...args);
      
      // En estado semi-abierto, un éxito restablece el circuito
      if (this.state === 'HALF_OPEN') {
        this.successCount++;
        
        if (this.successCount >= 2) {
          this.reset();
        }
      }
      
      return result;
    } catch (error) {
      this.recordFailure(error);
      throw error;
    }
  }
  
  recordFailure(error) {
    this.failureCount++;
    this.lastFailureTime = Date.now();
    
    if (this.state === 'CLOSED' && this.failureCount >= this.failureThreshold) {
      this.state = 'OPEN';
      console.warn('Circuit breaker state: OPEN');
    }
    
    console.error(`Service call failed (${this.failureCount} failures)`, error);
  }
  
  reset() {
    this.state = 'CLOSED';
    this.failureCount = 0;
    this.successCount = 0;
    console.log('Circuit breaker reset: CLOSED');
  }
}

// Uso
const apiClient = new CircuitBreaker(
  (endpoint) => fetch(`https://api.example.com/${endpoint}`).then(r => r.json()),
  { failureThreshold: 3, resetTimeout: 10000 }
);

async function getUserData(userId) {
  try {
    return await apiClient.execute(`users/${userId}`);
  } catch (error) {
    if (error.message.includes('Circuit is OPEN')) {
      return getCachedUserData(userId);
    }
    throw error;
  }
}

El manejo efectivo de excepciones en contextos asíncronos requiere una combinación de técnicas específicas para cada patrón de asincronía. Al implementar estas estrategias, podemos crear aplicaciones JavaScript que no solo funcionan correctamente en condiciones ideales, sino que también son resilientes frente a fallos en entornos distribuidos y redes poco confiables.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende JavaScript online

Ejercicios de esta lección Excepciones

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.

Funciones flecha

JavaScript
Puzzle

Polimorfismo

JavaScript
Test

Array

JavaScript
Código

Transformación con map()

JavaScript
Test

Gestor de tareas con JavaScript

JavaScript
Proyecto

Manipulación DOM

JavaScript
Test

Funciones

JavaScript
Test

Funciones flecha

JavaScript
Código

Async / Await

JavaScript
Código

Creación y uso de variables

JavaScript
Test

Excepciones

JavaScript
Puzzle

Promises

JavaScript
Código

Funciones cierre (closure)

JavaScript
Test

Herencia

JavaScript
Puzzle

Herencia

JavaScript
Test

Estructuras de control

JavaScript
Código

Selección de elementos DOM

JavaScript
Test

Modificación de elementos DOM

JavaScript
Test

Filtrado con filter() y find()

JavaScript
Test

Funciones cierre (closure)

JavaScript
Puzzle

Funciones

JavaScript
Puzzle

Mapas con Map

JavaScript
Test

Reducción con reduce()

JavaScript
Test

Callbacks

JavaScript
Puzzle

Manipulación DOM

JavaScript
Puzzle

Promises

JavaScript
Test

Async / Await

JavaScript
Test

Eventos del DOM

JavaScript
Puzzle

Async / Await

JavaScript
Puzzle

Promises

JavaScript
Puzzle

Filtrado con filter() y find()

JavaScript
Código

Callbacks

JavaScript
Test

Creación de clases y objetos Restaurante

JavaScript
Código

Reducción con reduce()

JavaScript
Código

Filtrado con filter() y find()

JavaScript
Puzzle

Reducción con reduce()

JavaScript
Puzzle

Conjuntos con Set

JavaScript
Puzzle

Herencia de clases

JavaScript
Código

Eventos del DOM

JavaScript
Test

Clases y objetos

JavaScript
Puzzle

Modificación de elementos DOM

JavaScript
Puzzle

Mapas con Map

JavaScript
Puzzle

Introducción a JavaScript

JavaScript
Test

Funciones

JavaScript
Código

Tipos de datos

JavaScript
Test

Clases y objetos

JavaScript
Test

Array

JavaScript
Test

Conjuntos con Set

JavaScript
Test

Array

JavaScript
Puzzle

Encapsulación

JavaScript
Puzzle

Clases y objetos

JavaScript
Código

Uso de operadores

JavaScript
Puzzle

Uso de operadores

JavaScript
Test

Estructuras de control

JavaScript
Test

Excepciones

JavaScript
Test

Transformación con map()

JavaScript
Puzzle

Funciones flecha

JavaScript
Test

Selección de elementos DOM

JavaScript
Puzzle

Encapsulación

JavaScript
Test

Mapas con Map

JavaScript
Código

Creación y uso de variables

JavaScript
Puzzle

Polimorfismo

JavaScript
Puzzle

Tipos de datos

JavaScript
Puzzle

Estructuras de control

JavaScript
Puzzle

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.

Accede GRATIS a JavaScript y certifícate

Certificados de superación de JavaScript

Supera todos los ejercicios de programación del curso de JavaScript y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje de esta lección

  1. Comprender qué son las excepciones y por qué son importantes en JavaScript.
  2. Conocer la estructura try-catch-finally y cómo se utiliza para manejar excepciones.
  3. Entender la función de cada bloque (try, catch, finally) en el manejo de excepciones.
  4. Aprender a lanzar y capturar excepciones usando la palabra clave throw.
  5. Saber cómo utilizar múltiples bloques catch para manejar diferentes tipos de excepciones.
  6. Comprender la utilidad del bloque finally para ejecutar código que debe ejecutarse siempre, independientemente de si ocurrió una excepción o no.
  7. Aprender a personalizar los mensajes de error en las excepciones para brindar información más específica.
  8. Entender cómo el manejo adecuado de excepciones mejora la robustez y la legibilidad del código.