Octobre rose : le dépistage précoce du cancer du sein sauve des vies. En savoir plus
JavaScript

Gérer les erreurs JavaScript : Try/catch et plus

Découvrez comment gérer efficacement les erreurs en JavaScript avec try/catch, promesses, async/await et des stratégies avancées de monitoring.

27 septembre 2026 12 min de lecture 99 vues 0 partages

Introduction

En 2025, JavaScript continue de dominer le paysage du développement web, mais la gestion des erreurs demeure un défi majeur pour de nombreux développeurs. Selon une étude récente de Rollbar, près de 60 % des développeurs déclarent que les erreurs non gérées ralentissent considérablement leurs projets et augmentent les coûts de maintenance. Comprendre comment prévenir et gérer ces erreurs est donc crucial pour maintenir des applications robustes, fiables et performantes.

Une erreur non interceptée peut provoquer un crash complet de votre application, dégrader l'expérience utilisateur ou même exposer des données sensibles. Que vous développiez une application monopage complexe, une API Node.js ou un simple site interactif, la maîtrise de la gestion des erreurs est une compétence fondamentale qui distingue un développeur junior d'un développeur senior.

Dans cet article approfondi, nous allons explorer les techniques essentielles et avancées pour gérer les erreurs en JavaScript. Vous apprendrez à utiliser le bloc try/catch, à définir des erreurs personnalisées, à exploiter les promesses et async/await pour la gestion des erreurs asynchrones, et à implémenter des pratiques avancées de journalisation et de monitoring pour améliorer la résilience de vos applications en production.

Comprendre le bloc try/catch

Le bloc try/catch constitue la méthode fondamentale pour gérer les erreurs en JavaScript. Il vous permet d'intercepter les erreurs survenant dans une portion de code spécifique, évitant ainsi que l'application ne plante brutalement et offrant la possibilité de réagir de manière contrôlée.

Syntaxe de base et fonctionnement

Le principe est simple : vous placez le code susceptible de générer une erreur dans le bloc try. Si une exception est levée durant l'exécution de ce bloc, le flux de contrôle passe immédiatement au bloc catch, qui reçoit l'objet d'erreur en paramètre.

try {
  const resultat = JSON.parse('chaîne invalide {');
  console.log(resultat);
} catch (error) {
  console.error('Une erreur est survenue :', error.message);
  // Affiche : Une erreur est survenue : Unexpected token c in JSON at position 0
}

Dans cet exemple concret, la tentative de parser une chaîne JSON invalide génère une SyntaxError. Sans le bloc try/catch, cette erreur aurait interrompu l'exécution du script. Grâce à l'interception, l'application continue de fonctionner et l'erreur est consignée proprement.

L'objet Error et ses propriétés

Lorsqu'une erreur est capturée, l'objet Error fournit plusieurs propriétés utiles pour le diagnostic :

  • message : description textuelle de l'erreur
  • name : le type d'erreur (TypeError, RangeError, SyntaxError, etc.)
  • stack : la pile d'appels au moment de l'erreur, essentielle pour le débogage
try {
  const obj = null;
  obj.propriete;
} catch (error) {
  console.log('Nom :', error.name);       // TypeError
  console.log('Message :', error.message); // Cannot read properties of null
  console.log('Stack :', error.stack);     // Trace complète de la pile
}

Utilisation du bloc finally

Le bloc finally s'exécute systématiquement après le try et le catch, qu'une erreur soit survenue ou non. C'est particulièrement utile pour les tâches de nettoyage, comme fermer des connexions à une base de données, libérer des ressources ou réinitialiser un état.

let connexion = null;

try {
  connexion = ouvrirConnexionBDD();
  const donnees = connexion.requete('SELECT * FROM utilisateurs');
  traiterDonnees(donnees);
} catch (error) {
  console.error('Erreur lors de la requête :', error.message);
  envoyerAlerte(error);
} finally {
  if (connexion) {
    connexion.fermer();
    console.log('Connexion fermée proprement.');
  }
}

Dans ce scénario, même si la requête échoue, la connexion à la base de données sera toujours fermée grâce au bloc finally. Cela prévient les fuites de ressources qui peuvent dégrader les performances de votre serveur.

Types d'erreurs natifs en JavaScript

JavaScript définit plusieurs types d'erreurs natifs, chacun correspondant à un problème spécifique. Connaître ces types vous aide à écrire des blocs catch plus précis :

try {
  let x = undefined;
  x.toString();
} catch (error) {
  if (error instanceof TypeError) {
    console.error('Erreur de type :', error.message);
  } else if (error instanceof ReferenceError) {
    console.error('Référence manquante :', error.message);
  } else if (error instanceof SyntaxError) {
    console.error('Erreur de syntaxe :', error.message);
  } else {
    console.error('Erreur inattendue :', error.message);
  }
}

Cette approche conditionnelle permet de traiter chaque type d'erreur de manière différenciée et d'apporter une réponse adaptée à chaque situation.

Erreurs personnalisées

JavaScript permet de créer des erreurs personnalisées en étendant la classe native Error. Cette technique est essentielle pour représenter des problèmes métier spécifiques à votre application et faciliter le débogage en équipe.

Créer une classe d'erreur personnalisée

class ValidationError extends Error {
  constructor(champ, message) {
    super(message);
    this.name = 'ValidationError';
    this.champ = champ;
    this.date = new Date();
  }
}

class AuthenticationError extends Error {
  constructor(message, codeErreur) {
    super(message);
    this.name = 'AuthenticationError';
    this.codeErreur = codeErreur;
  }
}

En ajoutant des propriétés supplémentaires comme champ, date ou codeErreur, vous enrichissez le contexte de l'erreur, ce qui simplifie considérablement le diagnostic en production.

Utilisation pratique des erreurs personnalisées

function validerUtilisateur(utilisateur) {
  if (!utilisateur.email) {
    throw new ValidationError('email', 'L\'adresse e-mail est obligatoire');
  }
  if (utilisateur.age < 18) {
    throw new ValidationError('age', 'L\'utilisateur doit être majeur');
  }
  return true;
}

try {
  validerUtilisateur({ nom: 'Jean', age: 16 });
} catch (error) {
  if (error instanceof ValidationError) {
    console.error(`Erreur de validation sur le champ "${error.champ}" : ${error.message}`);
  } else {
    console.error('Erreur inattendue :', error.message);
  }
}

Cette structure rend votre code métier beaucoup plus expressif. Au lieu d'un message d'erreur générique, vous obtenez une information précise sur le champ concerné et la nature du problème.

Gestion des erreurs avec les promesses

Les promesses constituent le mécanisme principal pour gérer les opérations asynchrones en JavaScript moderne. Cependant, elles introduisent des défis spécifiques en matière de gestion des erreurs, car les blocs try/catch classiques ne peuvent pas intercepter les erreurs survenant dans du code asynchrone basé sur les promesses.

Capturer les erreurs avec .catch()

La méthode .catch() permet de gérer les erreurs survenant à n'importe quel point d'une chaîne de promesses :

fetch('https://api.example.com/utilisateurs')
  .then(response => {
    if (!response.ok) {
      throw new Error(`Erreur HTTP : ${response.status}`);
    }
    return response.json();
  })
  .then(data => {
    console.log('Données reçues :', data);
  })
  .catch(error => {
    console.error('Erreur de récupération :', error.message);
  })
  .finally(() => {
    console.log('Requête terminée.');
  });

Notez l'utilisation de .finally() sur les promesses, qui fonctionne de manière analogue au bloc finally de try/catch et s'exécute quelle que soit l'issue de la promesse.

Erreurs courantes avec les promesses

L'une des erreurs les plus fréquentes consiste à oublier d'ajouter un .catch() à la fin de votre chaîne de promesses. Cela provoque des rejets de promesses non gérés qui, depuis Node.js 15, peuvent faire crasher votre application :

// MAUVAISE PRATIQUE : promesse sans .catch()
fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => traiterDonnees(data));
// Si l'API est indisponible, l'erreur sera silencieuse !

// BONNE PRATIQUE : toujours ajouter .catch()
fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => traiterDonnees(data))
  .catch(error => gererErreur(error));

Gérer les promesses multiples

Lorsque vous travaillez avec plusieurs promesses simultanées, Promise.allSettled() est souvent préférable à Promise.all(), car il ne rejette pas au premier échec :

const promesses = [
  fetch('https://api.example.com/utilisateurs'),
  fetch('https://api.example.com/produits'),
  fetch('https://api.example.com/commandes')
];

Promise.allSettled(promesses)
  .then(resultats => {
    resultats.forEach((resultat, index) => {
      if (resultat.status === 'fulfilled') {
        console.log(`Requête ${index + 1} réussie`);
      } else {
        console.error(`Requête ${index + 1} échouée :`, resultat.reason.message);
      }
    });
  });

Cette approche vous permet de traiter chaque résultat individuellement, sans qu'un échec unique ne bloque l'ensemble des opérations.

Utilisation d'async/await

La syntaxe async/await, introduite avec ES2017, simplifie considérablement l'écriture et la lecture du code asynchrone. Combinée avec try/catch, elle offre une gestion des erreurs asynchrones naturelle et intuitive.

Utilisez toujours try/catch avec async/await pour éviter les erreurs silencieuses qui peuvent être extrêmement difficiles à déboguer en production.

Pattern de base avec async/await

async function recupererUtilisateur(id) {
  try {
    const response = await fetch(`https://api.example.com/utilisateurs/${id}`);
    
    if (!response.ok) {
      throw new Error(`Utilisateur non trouvé (HTTP ${response.status})`);
    }
    
    const utilisateur = await response.json();
    return utilisateur;
  } catch (error) {
    console.error('Erreur lors de la récupération :', error.message);
    return null;
  }
}

Gestion d'erreurs granulaire avec async/await

Pour un contrôle plus fin, vous pouvez imbriquer plusieurs blocs try/catch ou combiner await avec .catch() directement :

async function traiterCommande(commandeId) {
  let utilisateur, produit;

  try {
    utilisateur = await recupererUtilisateur(commandeId);
  } catch (error) {
    throw new Error(`Impossible de charger l'utilisateur : ${error.message}`);
  }

  try {
    produit = await recupererProduit(commandeId);
  } catch (error) {
    throw new Error(`Impossible de charger le produit : ${error.message}`);
  }

  return { utilisateur, produit };
}

Patron de réessai (retry pattern)

Dans les applications en production, il est courant d'implémenter un mécanisme de réessai pour les opérations réseau instables :

async function fetchAvecRetry(url, tentativesMax = 3, delai = 1000) {
  for (let tentative = 1; tentative <= tentativesMax; tentative++) {
    try {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      return await response.json();
    } catch (error) {
      console.warn(`Tentative ${tentative}/${tentativesMax} échouée : ${error.message}`);
      
      if (tentative === tentativesMax) {
        throw new Error(`Échec après ${tentativesMax} tentatives : ${error.message}`);
      }
      
      await new Promise(resolve => setTimeout(resolve, delai * tentative));
    }
  }
}

Ce patron utilise un délai exponentiel entre les tentatives, ce qui est une pratique recommandée pour ne pas surcharger un serveur déjà en difficulté.

Gestion globale des erreurs

En complément de la gestion locale avec try/catch, il est essentiel de mettre en place des gestionnaires d'erreurs globaux pour capturer les erreurs qui auraient échappé à votre code.

Dans le navigateur

// Capturer les erreurs JavaScript non gérées
window.addEventListener('error', (event) => {
  console.error('Erreur globale :', event.message);
  console.error('Fichier :', event.filename, 'Ligne :', event.lineno);
  envoyerAuServiceDeMonitoring({
    type: 'erreur_js',
    message: event.message,
    source: event.filename,
    ligne: event.lineno
  });
});

// Capturer les rejets de promesses non gérés
window.addEventListener('unhandledrejection', (event) => {
  console.error('Promesse rejetée non gérée :', event.reason);
  event.preventDefault();
});

Dans Node.js

process.on('uncaughtException', (error) => {
  console.error('Exception non capturée :', error.message);
  // Journaliser l'erreur puis arrêter proprement le processus
  process.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
  console.error('Rejet non géré :', reason);
});

Ces gestionnaires globaux représentent votre dernier filet de sécurité. Ils ne doivent pas remplacer une gestion locale rigoureuse, mais ils garantissent qu'aucune erreur critique ne passe inaperçue.

Pratiques avancées pour une gestion robuste

Pour garantir la résilience de vos applications en production, il est important d'adopter des stratégies avancées qui vont au-delà de la simple interception d'erreurs.

Journalisation structurée des erreurs

Une journalisation efficace doit inclure suffisamment de contexte pour permettre un diagnostic rapide :

function journaliserErreur(error, contexte = {}) {
  const entree = {
    horodatage: new Date().toISOString(),
    nom: error.name,
    message: error.message,
    stack: error.stack,
    contexte: contexte,
    environnement: process.env.NODE_ENV || 'development',
    url: typeof window !== 'undefined' ? window.location.href : 'serveur'
  };
  
  // Envoyer vers un service de monitoring
  if (entree.environnement === 'production') {
    envoyerVersServiceExterne(entree);
  }
  
  console.error(JSON.stringify(entree, null, 2));
}

Tableau comparatif des techniques

TechniqueContexte d'utilisationAvantagesInconvénients
Try/CatchErreurs synchronesSimple à implémenter, natifNe couvre pas les erreurs asynchrones
Erreurs personnaliséesLogique métierClarté des messages, contexte richeNécessite une architecture réfléchie
.catch() sur promessesChaînes de promessesGestion des erreurs asynchronesRisque d'oubli en fin de chaîne
Async/Await + Try/CatchCode asynchrone moderneCode lisible et maintenableRequiert ES2017+
Gestionnaires globauxFilet de sécuritéCapture tout ce qui est non géréManque de contexte local
Retry PatternOpérations réseauRésilience aux pannes temporairesPeut masquer des problèmes permanents

Pièges et erreurs courantes à éviter

Même les développeurs expérimentés commettent des erreurs dans la gestion des exceptions. Voici les pièges les plus fréquents et comment les éviter :

  • Oublier d'attraper les erreurs dans les promesses : ajoutez systématiquement un .catch() ou utilisez async/await avec try/catch
  • Avaler les erreurs silencieusement : un bloc catch vide est pire que l'absence de gestion, car l'erreur disparaît sans laisser de trace
  • Ne pas utiliser try/catch avec async/await : une fonction async sans try/catch retourne une promesse rejetée qui peut passer inaperçue
  • Créer des erreurs personnalisées sans conserver le stack trace : appelez toujours super(message) dans le constructeur
  • Ignorer les erreurs de validation des données d'entrée : validez toutes les entrées utilisateur avant de les traiter
  • Exposer des détails techniques aux utilisateurs finaux : affichez des messages conviviaux en front-end et consignez les détails techniques côté serveur
// MAUVAISE PRATIQUE : catch vide
try {
  operationRisquee();
} catch (e) {
  // Ne rien faire ici est dangereux !
}

// BONNE PRATIQUE : journaliser et réagir
try {
  operationRisquee();
} catch (error) {
  journaliserErreur(error, { action: 'operationRisquee' });
  afficherMessageUtilisateur('Une erreur inattendue est survenue. Veuillez réessayer.');
}

Conclusion

En maîtrisant la gestion des erreurs en JavaScript, vous améliorez considérablement la robustesse, la fiabilité et la maintenabilité de vos applications. Les techniques abordées dans cet article, du simple try/catch aux erreurs personnalisées, en passant par les promesses, async/await et les gestionnaires globaux, constituent un arsenal complet pour faire face à toutes les situations.

Voici les points essentiels à retenir :

  • Utilisez try/catch pour intercepter les erreurs synchrones et le combiner avec finally pour le nettoyage
  • Créez des erreurs personnalisées pour enrichir le contexte et faciliter le diagnostic
  • Gérez les erreurs asynchrones avec .catch() sur les promesses ou async/await avec try/catch
  • Mettez en place des gestionnaires globaux comme filet de sécurité
  • Implémentez une journalisation structurée pour le monitoring en production
  • Utilisez le retry pattern pour les opérations réseau sujettes à des pannes temporaires
  • Ne laissez jamais un bloc catch vide : consignez toujours l'erreur

Pour renforcer vos compétences, nous vous encourageons à mettre en pratique ces techniques dans vos projets actuels. Commencez par auditer votre code existant pour identifier les endroits où les erreurs ne sont pas correctement gérées, puis implémentez progressivement les stratégies décrites dans cet article. Une gestion des erreurs solide est un investissement qui se rentabilise rapidement en temps de débogage économisé.

#JavaScript #Erreurs #Bonnes pratiques

Partager cet article

Jordane YENO
Jordane YENO

Développeur web et mobile à Port-Gentil et Libreville. J'écris sur ce que je construis et ce que j'apprends.

En savoir plus
À lire ensuite

Articles similaires

Retour au blog