50% OFF Plus
--:--:--
¡Obtener!

Excepciones

Avanzado
JavaScript
JavaScript
Actualizado: 14/05/2025

¡Desbloquea el curso de JavaScript completo!

IA
Ejercicios
Certificado
Entrar

Mira la lección en vídeo

Accede al vídeo completo de esta lección y a más contenido exclusivo con el Plan Plus.

Desbloquear Plan Plus

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

Guarda tu progreso

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

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.

Aprendizajes de esta lección de JavaScript

  • Entender el concepto de excepciones en JavaScript.
  • Aprender a utilizar el bloque try-catch-finally.
  • Capturar y lanzar errores de manera adecuada.
  • Diferenciar los tipos de errores nativos.
  • Aplicar buenas prácticas en el manejo de excepciones.
  • Diseñar estrategias robustas para el control de errores.
  • Implementar manejo de errores en contextos asíncronos.

Completa este curso de JavaScript y certifícate

Únete a nuestra plataforma de cursos de programación y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración