Tutos & formations
Gestion des erreurs JavaScript

Gestion des erreurs JavaScript


Guide pour implémenter une gestion d'erreurs robuste dans votre thème Shopify, avec des techniques de débogage et de monitoring.

Vue d'ensemble

Une bonne gestion des erreurs JavaScript permet de :

  • Améliorer l'expérience utilisateur
  • Faciliter le débogage
  • Éviter que le site se casse complètement
  • Collecter des informations pour corriger les bugs

Gestion d'erreurs de base

Try-Catch

La méthode la plus basique pour capturer les erreurs :

try {
  // Code qui peut échouer
  const result = riskyFunction();
  console.log(result);
} catch (error) {
  // Gérer l'erreur
  console.error('Erreur:', error.message);
  // Optionnel : afficher un message à l'utilisateur
}

Try-Catch avec Finally

Le bloc finally s'exécute toujours, même en cas d'erreur :

try {
  // Code
  processData();
} catch (error) {
  console.error('Erreur:', error);
} finally {
  // Code de nettoyage qui s'exécute toujours
  cleanup();
}

Gestion d'erreurs asynchrones

Pour les promesses :

// Avec .then() et .catch()
fetch('/api/data')
  .then(response => response.json())
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('Erreur de requête:', error);
  });
 
// Avec async/await
async function fetchData() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Erreur de requête:', error);
    // Retourner une valeur par défaut ou relancer l'erreur
    throw error;
  }
}

Handler global d'erreurs

window.onerror

Capturer toutes les erreurs non gérées :

window.onerror = function(message, source, lineno, colno, error) {
  console.error('Erreur globale:', {
    message: message,
    source: source,
    line: lineno,
    column: colno,
    error: error
  });
  
  // Envoyer à un service de logging (optionnel)
  logErrorToService({
    message,
    source,
    line: lineno,
    column: colno,
    stack: error?.stack
  });
  
  // Retourner false pour ne pas afficher l'erreur dans la console
  return false;
};

unhandledrejection

Pour les promesses rejetées non gérées :

window.addEventListener('unhandledrejection', function(event) {
  console.error('Promesse rejetée non gérée:', event.reason);
  
  // Prévenir le comportement par défaut (affichage dans la console)
  event.preventDefault();
  
  // Logger l'erreur
  logErrorToService({
    type: 'unhandledrejection',
    reason: event.reason,
    promise: event.promise
  });
});

Erreurs dans les event listeners

Wrapper pour capturer les erreurs dans les listeners :

function safeEventListener(element, event, handler) {
  element.addEventListener(event, function(event) {
    try {
      handler(event);
    } catch (error) {
      console.error(`Erreur dans le handler ${event}:`, error);
      handleError(error);
    }
  });
}
 
// Utilisation
safeEventListener(document, 'click', function(e) {
  // Votre code qui peut échouer
  riskyCode();
});

Classe utilitaire pour la gestion d'erreurs

Créer une classe centralisée pour gérer les erreurs :

class ErrorHandler {
  constructor(options = {}) {
    this.logToConsole = options.logToConsole !== false;
    this.logToService = options.logToService || null;
    this.showUserMessage = options.showUserMessage || false;
    this.setupGlobalHandlers();
  }
 
  setupGlobalHandlers() {
    // Handler pour les erreurs synchrones
    window.onerror = (message, source, lineno, colno, error) => {
      this.handleError({
        message,
        source,
        line: lineno,
        column: colno,
        stack: error?.stack,
        type: 'error'
      });
      return false;
    };
 
    // Handler pour les promesses rejetées
    window.addEventListener('unhandledrejection', (event) => {
      this.handleError({
        message: event.reason?.message || String(event.reason),
        stack: event.reason?.stack,
        type: 'unhandledrejection',
        reason: event.reason
      });
      event.preventDefault();
    });
  }
 
  handleError(errorInfo) {
    // Logger dans la console
    if (this.logToConsole) {
      console.error('Erreur capturée:', errorInfo);
    }
 
    // Envoyer à un service externe
    if (this.logToService) {
      this.sendToService(errorInfo);
    }
 
    // Afficher un message à l'utilisateur
    if (this.showUserMessage) {
      this.showUserNotification();
    }
  }
 
  async sendToService(errorInfo) {
    try {
      await fetch(this.logToService, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          ...errorInfo,
          url: window.location.href,
          userAgent: navigator.userAgent,
          timestamp: new Date().toISOString()
        })
      });
    } catch (error) {
      console.error('Erreur lors de l\'envoi au service:', error);
    }
  }
 
  showUserNotification() {
    // Créer une notification discrète pour l'utilisateur
    const notification = document.createElement('div');
    notification.className = 'error-notification';
    notification.textContent = 'Une erreur est survenue. Veuillez réessayer.';
    notification.style.cssText = `
      position: fixed;
      bottom: 20px;
      right: 20px;
      background: #ff4444;
      color: white;
      padding: 12px 20px;
      border-radius: 4px;
      z-index: 10000;
      box-shadow: 0 2px 8px rgba(0,0,0,0.2);
    `;
    document.body.appendChild(notification);
    
    setTimeout(() => {
      notification.remove();
    }, 5000);
  }
 
  // Méthode pour logger manuellement
  log(message, data = {}) {
    this.handleError({
      message,
      ...data,
      type: 'manual'
    });
  }
}
 
// Initialisation
const errorHandler = new ErrorHandler({
  logToConsole: true,
  logToService: '/api/log-error', // Optionnel
  showUserMessage: true
});

Gestion d'erreurs spécifiques Shopify

Erreurs d'ajout au panier

async function addToCart(variantId, quantity = 1) {
  try {
    const response = await fetch('/cart/add.js', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        id: variantId,
        quantity: quantity
      })
    });
 
    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.description || 'Erreur lors de l\'ajout au panier');
    }
 
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Erreur ajout au panier:', error);
    
    // Afficher un message d'erreur à l'utilisateur
    showCartError(error.message);
    
    // Logger l'erreur
    errorHandler.log('Erreur ajout au panier', {
      variantId,
      quantity,
      error: error.message
    });
    
    throw error;
  }
}
 
function showCartError(message) {
  // Votre code pour afficher l'erreur dans l'UI
  const errorElement = document.querySelector('.cart-error-message');
  if (errorElement) {
    errorElement.textContent = message;
    errorElement.style.display = 'block';
  }
}

Erreurs de récupération de produits

async function fetchProduct(handle) {
  try {
    const response = await fetch(`/products/${handle}.js`);
    
    if (!response.ok) {
      throw new Error(`Produit non trouvé: ${handle}`);
    }
    
    const product = await response.json();
    return product;
  } catch (error) {
    console.error('Erreur récupération produit:', error);
    
    // Afficher un état d'erreur dans l'UI
    showProductError(handle);
    
    throw error;
  }
}
 
function showProductError(handle) {
  const productContainer = document.querySelector('.product-container');
  if (productContainer) {
    productContainer.innerHTML = `
      <div class="product-error">
        <p>Impossible de charger ce produit.</p>
        <a href="/collections/all">Voir tous les produits</a>
      </div>
    `;
  }
}

Erreurs de chargement de sections

async function loadSection(sectionId) {
  try {
    const response = await fetch(`/sections/${sectionId}`);
    
    if (!response.ok) {
      throw new Error(`Section ${sectionId} non trouvée`);
    }
    
    const html = await response.text();
    return html;
  } catch (error) {
    console.error('Erreur chargement section:', error);
    
    // Optionnel : charger une section de fallback
    return loadFallbackSection(sectionId);
  }
}
 
function loadFallbackSection(sectionId) {
  return '<div class="section-error">Contenu temporairement indisponible</div>';
}

Validation et prévention

Valider les données avant utilisation

function validateProductData(product) {
  if (!product) {
    throw new Error('Données produit manquantes');
  }
  
  if (!product.id) {
    throw new Error('ID produit manquant');
  }
  
  if (!product.variants || product.variants.length === 0) {
    throw new Error('Aucune variante disponible');
  }
  
  return true;
}
 
// Utilisation
try {
  validateProductData(productData);
  // Traiter les données
  processProduct(productData);
} catch (error) {
  console.error('Données invalides:', error.message);
}

Vérifier l'existence des éléments DOM

function safeQuerySelector(selector, errorMessage) {
  const element = document.querySelector(selector);
  
  if (!element) {
    const message = errorMessage || `Élément non trouvé: ${selector}`;
    console.warn(message);
    return null;
  }
  
  return element;
}
 
// Utilisation
const addToCartButton = safeQuerySelector('[data-add-to-cart]', 'Bouton ajout au panier non trouvé');
if (addToCartButton) {
  addToCartButton.addEventListener('click', handleAddToCart);
}

Vérifier les dépendances

function checkDependencies() {
  const dependencies = {
    Shopify: typeof Shopify !== 'undefined',
    theme: typeof theme !== 'undefined',
    jQuery: typeof $ !== 'undefined'
  };
  
  const missing = Object.entries(dependencies)
    .filter(([name, exists]) => !exists)
    .map(([name]) => name);
  
  if (missing.length > 0) {
    console.warn('Dépendances manquantes:', missing);
    return false;
  }
  
  return true;
}
 
// Vérifier avant d'exécuter le code
if (checkDependencies()) {
  initializeApp();
} else {
  console.error('Impossible d\'initialiser l\'application: dépendances manquantes');
}

Debugging avancé

Stack trace détaillée

function getDetailedStackTrace() {
  const stack = new Error().stack;
  console.log('Stack trace complet:', stack);
  return stack;
}
 
// Utilisation dans un catch
try {
  problematicFunction();
} catch (error) {
  console.error('Erreur:', error);
  console.error('Stack trace:', error.stack);
  getDetailedStackTrace();
}

Logger avec contexte

function logWithContext(message, context = {}) {
  const logData = {
    message,
    timestamp: new Date().toISOString(),
    url: window.location.href,
    userAgent: navigator.userAgent,
    ...context
  };
  
  console.log('Log avec contexte:', logData);
  return logData;
}
 
// Utilisation
try {
  addToCart(variantId);
} catch (error) {
  logWithContext('Erreur ajout au panier', {
    error: error.message,
    variantId,
    userId: getUserId()
  });
}

Bonnes pratiques

✅ À faire

  • Toujours utiliser try-catch pour les opérations critiques
  • Logger les erreurs avec suffisamment de contexte
  • Afficher des messages d'erreur clairs aux utilisateurs
  • Valider les données avant de les utiliser
  • Vérifier l'existence des éléments DOM avant manipulation

❌ À éviter

  • Ignorer silencieusement les erreurs
  • Afficher des messages d'erreur techniques aux utilisateurs
  • Bloquer l'exécution du reste du code en cas d'erreur
  • Ne pas logger les erreurs pour le débogage
  • Utiliser console.error en production sans filtrage

Monitoring en production

Intégration avec des services externes

Sentry

// Installation: npm install @sentry/browser
import * as Sentry from "@sentry/browser";
 
Sentry.init({
  dsn: "YOUR_SENTRY_DSN",
  environment: "production"
});
 
// Capturer une erreur manuellement
try {
  riskyOperation();
} catch (error) {
  Sentry.captureException(error);
}

LogRocket

// Installation et configuration LogRocket
LogRocket.init('YOUR_APP_ID');
 
// Logger une erreur
LogRocket.captureException(error);

Ressources