Gestion des Erreurs
Présentation
La gestion des erreurs permet de garantir la fiabilité, la stabilité et la convivialité des applications. En anticipant et en traitant les erreurs potentielles, le code est renforcé et plus robuste. Lorsqu'une erreur survient, la gestion appropriée de cette dernière permet d'éviter des interruptions inattendues, contribuant ainsi à la fiabilité globale du programme.
Une gestion des erreurs bien mise en œuvre facilitent le processus de débogage, permettant d'identifier plus rapidement et efficacement les sources d'erreurs et de les corriger. De plus, elle peut permettre d'améliorer l'experience utilisateur. Au lieu de laisser le programme échouer silencieusement, elle permet de fournir des messages d'erreur compréhensibles, aidant les utilisateurs à comprendre les problèmes rencontrés.
Une gestion appropriée des erreurs simplifie la maintenance du code en permettant une anticipation des changements à venir. Cette approche rend le code plus flexible et facilite l'intégration de nouvelles fonctionnalités ou la modification des dépendances.
La gestion des erreurs prévient également les effets en cascade qui pourraient découler d'une erreur non gérée. En intervenant dès l'apparition d'une erreur, il est possible d'éviter que des problèmes ne se propagent et de préserver ainsi l'intégrité globale du programme.
Finalement, la gestion des erreurs joue un rôle important dans la sécurité des applications en prévenant la divulgation d'informations sensibles.
Les Erreurs
Lorsqu'un programme s'exécute, il peut rencontrer des situations anormales qui empêchent son bon fonctionnement. Ces situations déclenchent des erreurs représentées par des objets de type Error et ses sous-classes comme ReferenceError, TypeError ou encore SyntaxError.
Un exemple courant d'erreur est lorsque l'on tente d'accéder à une variable qui n'a pas été déclarée. Dans ce cas, JavaScript lève une erreur de type ReferenceError, ce qui interrompt immédiatement l'exécution du programme :
const prenom = "Sayid";
// La variable nommée "variableNonDeclaree" n'ayant pas été déclarée, une erreur de référence sera levée.
console.log(variableNonDeclaree); // Affiche : Uncaught ReferenceError: variableNonDeclaree is not defined
// Le code suivant ne sera pas exécuté car l'erreur précédente a interrompu le programme.
console.log(prenom);
Capturer et Manipuler les Erreurs
Pour éviter une interruption brutale du programme et avoir la possibilité de réagir aux erreurs, il est envisageable de gérer les erreurs à l'aide des blocs try et catch
Le bloc try contient le code susceptible de générer une erreur. Si une erreur survient, l'exécution du code qui s'y trouve est interrompue et le bloc catch intercepte l'erreur. Ce dernier reçoit en argument un objet Error, qui contient des informations sur l'erreur, notamment son message et son type :
const prenom = "Sayid";
try
{
// La variable "variableNonDeclaree" n'ayant pas été déclarée, une erreur de référence sera levée.
console.log(variableNonDeclaree);
// Ce code ne sera pas exécuté, car l'erreur précédente a interrompu le bloc "try".
console.log(prenom);
}
catch (erreur)
{
console.error(`Une erreur s'est produite : ${erreur.message}`); // Affiche : Une erreur s'est produite : variableNonDeclaree is not defined
}
// Le reste du code s'exécute normalement, car l'erreur a été interceptée par "catch".
console.log('Le code situé après le bloc "try/catch" continue son exécution.'); // Affiche : Le code situé après le bloc "try/catch" continue son exécution.
Lever une Exception
Contrairement aux erreurs natives générées automatiquement par JavaScript, il est possible de lever manuellement une exception afin de signaler une condition anormale dans un programme. Cela se fait à l'aide de l'instruction throw. Une exception levée interrompt immédiatement l'exécution du programme, sauf si elle est capturée par un bloc try...catch.
En utilisant new Error(), on peut créer une nouvelle instance de l'objet Error, permettant d'inclure un message explicite décrivant la cause du problème. Cela facilite le débogage et permet d'informer clairement d'une situation incorrecte.
// Fonction qui effectue une division entre deux nombres.
function diviser(a, b)
{
// Vérifie si b est égal à zéro.
if (b === 0)
{
// Si b est égal à zéro, on lève une exception de type "Error".
// Contrairement aux erreurs natives, cette exception est créée et levée volontairement.
throw new Error('La division par zéro n\'est pas autorisée.');
}
// Effectue la division et renvoie le résultat.
return a / b;
}
// Valeurs à passer en arguments à la fonction "diviser()"
const dividende = 10;
const diviseur = 0;
// Tenter de diviser 10 par 0, ce qui lève une exception.
const resultat = diviser(dividende, diviseur);
/*
Affiche dans la console :
Uncaught Error: La division par zéro n'est pas autorisée.
*/
// Cette ligne ne sera pas exécutée car l'exception levée a interrompu l'exécution du programme.
console.log(resultat);
Pour éviter une interruption brutale du programme et avoir la possibilité de réagir aux erreurs, il est envisageable de gérer les erreurs à l'aide des blocs try et catch
// Fonction qui effectue une division entre deux nombres.
function diviser(a, b)
{
// Vérifie si b est égal à zéro.
if (b === 0)
{
// Si b est zéro, lance une erreur générique indiquant que la division par zéro n'est pas autorisée.
throw new Error('La division par zéro n\'est pas autorisée.');
}
// Effectue la division et renvoie le résultat.
return a / b;
}
// Valeurs à passer en arguments à la fonction "diviser()" :
const dividende = 10;
const diviseur = 0;
// Le bloc "catch" permettant de capturer les potentielles erreurs rencontrées dans le bloc "try" :
try
{
// Tenter de diviser 10 par 0, ce qui provoquera une erreur.
const resultat = diviser(dividende, diviseur);
// Cette instruction ne sera pas exécutée à cause de l'erreur levée
// lors de l'appel à la fonction "diviser(10, 0)".
console.log(resultat);
}
catch (erreur)
{
// Capture et gère les erreurs ici.
console.error('Erreur : ' + erreur.message);
}
// Cette instruction sera exécutée même en cas d'erreur, grâce à la gestion des exceptions
// effectuée avec les blocs "try" et "catch".
console.log('Code terminé !');
/*
Affiche :
Erreur : La division par zéro n'est pas autorisée.
Code terminé !
*/
Les Différents Types d'Erreurs
L'objet Error est couramment utilisé pour représenter des erreurs génériques. Cependant, il existe également des objets d'erreur plus spécifiques qui se manifestent sous plusieurs formes, chacune correspondant à un scénario spécifique :
- Error : Cette erreur survient lorsqu'une erreur générique doit être signalée.
- RangeError : Cette erreur survient lorsque qu'un nombre donné est utilisé en dehors de la plage autorisée.
- ReferenceError : Cette erreur survient lors de l'appel à une variable qui n'est pas accessible, par exemple, si elle n'a pas été déclarée.
- SyntaxError : Cette erreur survient en cas de syntaxe incorrecte. Elle peut englober des erreurs telles que des parenthèses manquantes.
- TypeError : Cette erreur survient lorsqu'une opération est effectuée sur un type de variable inattendu. Par exemple, tenter d'appeler une fonction sur une variable qui n'est pas une fonction.
- URIError : Cette erreur survient lorsqu'une fonction comme decodeURIComponent() est utilisée avec des arguments invalides.
Par exemple, il peut être nécessaire de lever une exception liée à un problème de typage. Plutôt que d'utiliser la classe d'erreur générique Error, on peut recourir à la classe TypeError :
const a = 'cinq';
const b = 2
// Vérifie si a et b sont des nombres.
if (typeof a !== 'number' || typeof b !== 'number')
{
// Si l'une des deux valeurs n'est pas un nombre, lève une exception de typage avec un message personnalisé.
throw new TypeError('Les valeurs entrées doivent être des nombres.'); // Affiche : TypeError: Les valeurs entrées doivent être des nombres.
}
L'avantage de cette approche est qu'elle permet de centraliser la gestion des erreurs tout en offrant la possibilité de réagir en fonction du type d'erreur rencontré. Pour connaître la classe à partir de laquelle un objet a été instancié, on peut utiliser l'opérateur instanceof.
// Fonction qui effectue une division entre deux nombres.
function diviser(a, b)
{
// Vérifie si a et b sont des nombres.
if (typeof a !== 'number' || typeof b !== 'number')
{
// Si l'un des deux n'est pas un nombre, lance une erreur de type.
throw new TypeError('Les valeurs entrées doivent être des nombres.');
}
// Vérifie si b est égal à zéro.
if (b === 0)
{
// Si b est zéro, lance une erreur générique indiquant que la division par zéro n'est pas autorisée.
throw new Error('La division par zéro n\'est pas autorisée.');
}
// Effectue la division et renvoie le résultat.
return a / b;
}
// Valeurs à passer en arguments à la fonction "diviser()" :
const dividende = 'cinq';
const diviseur = 2;
try
{
// Tenter de diviser 'cinq' par 2, ce qui provoquera une erreur.
const resultat = diviser(dividende, diviseur);
// Cette instruction ne sera pas exécutée à cause de l'erreur levée
// lors de l'appel à la fonction "diviser('cinq', 2)".
console.log(resultat);
}
catch (erreur)
{
// Capture et gère les erreurs en fonction de leur type :
if (erreur instanceof TypeError)
{
// Si l'erreur est de type TypeError, affiche un message d'erreur spécifique.
console.error('Erreur de type : ' + erreur.message);
}
else
{
// Si l'erreur est de tout autre type, affiche un message d'erreur générique.
console.error('Erreur : ' + erreur.message);
}
}
// Affiche : Erreur de type : Les valeurs entrées doivent être des nombres.
Les Types d'Erreurs Personnalisés
Il est possible de créer des erreurs personnalisées en étendant la classe native Error ou l'une de ses sous-classes. Cela permet de personnaliser les messages d'erreur et d'ajouter des propriétés supplémentaires pour une identification et une gestion plus précises des erreurs dans le code.
// Définition d'une classe d'erreurs personnalisée qui hérite de la classe "Error".
class MonErreurPersonnalisee extends Error {}
Il est bien entendu possible d'approfondir davantage en ajoutant de nouvelles propriétés et méthodes à la classe. Cependant, même avec cette déclaration simple, nous sommes déjà en mesure de gérer les erreurs de ce type à l'aide de l'instruction instanceof, comme nous l'avons evoqué précédemment.
try
{
// Provoquer intentionnelement une erreur de type "MonErreurPersonnalisee".
throw new MonErreurPersonnalisee('J\'ai tout cassé!');
}
catch (erreur)
{
// Capture et gère les erreurs en fonction de leur type :
if (erreur instanceof MonErreurPersonnalisee)
{
console.error('Erreur personnalisée : ' + erreur.message)
}
else if (erreur instanceof TypeError)
{
console.error('Erreur de type : ' + erreur.message);
}
else
{
console.error('Erreur : ' + erreur.message);
}
}
// Affiche : Erreur personnalisée : J'ai tout cassé!
La Propagation des Erreurs
La gestion des erreurs offre la possibilité de propager les erreurs à travers plusieurs niveaux d'appels dans votre programme. Cela signifie que lorsqu'une erreur survient à un niveau particulier de votre code, elle peut être détectée et gérée à différents niveaux supérieurs de la hiérarchie d'appel. Cette capacité à faire remonter les erreurs permet de créer des mécanismes de gestion d'erreurs centralisés facilitant ainsi la maintenance.
// Niveau 3
function calculerSomme(nombres)
{
let somme = 0;
for (const nombre of nombres)
{
// Vérifie si chaque élément du tableau "nombres" est un nombre valide.
if (typeof nombre === 'number')
{
// Si c'est le cas, ajoute le nombre à la somme.
somme += nombre;
}
else
{
// Si l'élément n'est pas un nombre, génère une erreur de type "TypeError".
throw new TypeError('Les valeurs du tableau "nombres" doivent être de type "number" !');
}
}
return somme;
}
// Niveau 2
function calculerMoyenne(nombres)
{
// Vérifie si "nombres" est un tableau.
if (!Array.isArray(nombres))
{
// Si ce n'est pas un tableau, génère une erreur de type "TypeError".
throw new TypeError('La variable "nombres" doit être un tableau !');
}
// Vérifie si le tableau n'est pas vide.
if (nombres.length === 0)
{
// Si le tableau est vide, génère une erreur de type "Error".
throw new Error('Le tableau "nombres" ne doit pas être vide !');
}
// Appelle la fonction calculerSomme pour obtenir la somme des nombres.
const somme = calculerSomme(nombres);
// Calcule la moyenne en divisant la somme par la taille du tableau.
return somme / nombres.length;
}
// Niveau 1
const nombresDontOnVeutLaMoyenne = [10, 'cinq', 8, 2];
try
{
// Appelle la fonction calculerMoyenne avec le tableau de nombres.
const moyenne = calculerMoyenne(nombresDontOnVeutLaMoyenne);
// Affiche la moyenne calculée.
console.log(`Le résultat de la moyenne de ${nombresDontOnVeutLaMoyenne} vaut ${moyenne}`);
}
catch (erreur)
{
// Les erreurs provenant des niveaux inférieurs sont capturées ici.
if (erreur instanceof TypeError)
{
// Si l'erreur est de type TypeError, affiche un message d'erreur spécifique.
console.error('Erreur de type : ' + erreur.message);
}
else
{
// Si l'erreur est de tout autre type, affiche un message d'erreur générique.
console.error('Erreur : ' + erreur.message);
}
// Afficher liste ordonnée des fonctions qui ont été appelées pour atteindre le point où l'erreur a été générée.
console.error(erreur.stack);
}
/*
Affiche :
Erreur de type : Les valeurs du tableau "nombres" doivent être de type "number" !
calculerSomme@source:25:39
calculerMoyenne@source:53:57
@source:70:57
*/
Le Bloc Finally
Finalement, notez qu'il existe un type de bloc, à savoir le bloc finally. Celui-ci est intégré dans une structure try...catch pour définir un ensemble d'instructions qui sera systématiquement exécuté, qu'une exception soit déclenchée ou non. Cela implique que les instructions contenues dans le bloc "finally" seront toujours exécutées, que ce soit après la gestion d'une exception avec le bloc catch ou simplement après l'exécution normale du code situé dans le bloc try :
try
{
// Code susceptible de générer une exception...
console.log("Bloc try : Début de l'exécution");
// Générer intentionnellement une erreur pour la démonstration.
throw new Error("Une erreur s'est produite !");
}
catch (erreur)
{
console.error("Bloc catch : Une erreur a été attrapée : " + erreur.message);
}
finally
{
console.log("Bloc finally : Toujours exécuté, que l'erreur soit survenue ou non.");
}
/*
Affiche :
Bloc try : Début de l'exécution
Bloc catch : Une erreur a été attrapée : Une erreur s'est produite !
Bloc finally : Toujours exécuté, que l'erreur soit survenue ou non.
*/
Pourquoi Utiliser les Blocs Try...Catch ?
L'utilisation de blocs try...catch pour gérer les erreurs présente plusieurs avantages significatifs par rapport à l'utilisation de console.log() au sein de conditions pour le débogage.
Tout d'abord, les blocs try...catch sont spécialement conçus pour traiter les erreurs de manière explicite. Ils vous permettent d'anticiper des erreurs potentielles, de les attraper et de prendre des mesures appropriées en réponse. Cette approche améliore considérablement la robustesse de votre code en vous permettant de réagir aux erreurs de manière contrôlée et prévue.
De plus, l'utilisation de try...catch contribue à la lisibilité de votre code. En séparant clairement la logique de gestion des erreurs du code principal, vous facilitez la compréhension de votre code, ce qui est particulièrement utile lorsque plusieurs personnes travaillent sur un projet ou lorsque vous devez réviser votre code à l'avenir.
Une autre grande force des blocs try...catch réside dans leur capacité à offrir un meilleur contrôle sur la manière dont les erreurs sont gérées et signalées. Vous pouvez choisir de les enregistrer, de les journaliser, de les afficher à l'utilisateur ou de prendre des mesures spécifiques en fonction du type d'erreur. Cela permet une personnalisation précise de la gestion des erreurs en fonction des besoins de votre application.
En outre, lorsque vous travaillez avec des opérations asynchrones, telles que des requêtes AJAX ou des promesses, try...catch permet de capturer des erreurs qui se produisent dans le contexte asynchrone et les gérer correctement. Cela garantit que les erreurs asynchrones n'interrompent pas le flux de votre application et qu'elles sont correctement traitées.
Finallement, lorsqu'une erreur survient dans un bloc try, le flux de contrôle est immédiatement transféré au bloc catch correspondant, où vous pouvez gérer l'erreur de manière appropriée. Cela signifie que l'erreur n'interrompt pas l'exécution globale du script, ce qui est crucial pour éviter que l'ensemble de l'application ne cesse de fonctionner en cas d'erreur dans une partie spécifique du code.
Exercices
Exo-gestion-des-erreurs-01
- Objectif : Identifier une erreur qui interrompt l'exécution du programme.
- Instructions :
- Créez un nouveau dossier nommé exo-gestion-des-erreurs-01 pour votre projet.
- À la racine du projet, créez un dossier nommé scripts.
- Dans le dossier scripts, créez un nouveau fichier nommé app.js.
- Dans le fichier app.js, copiez le code suivant :
// La méthode "sqrt" de l'objet "Math" retourne la racine carrée // du nombre lui étant passé en argument. const racineCarreeDeNombre = Math.sqrt(nombre); console.log(racineCarreeDeNombre); console.log("Fin du traitement!"); - À la racine du projet, créez un fichier index.html contenant une structure HTML basique, puis importez-y le fichier script app.js que vous venez de créer.
- Ouvrez l'application dans votre navigateur et observez la console pour analyser le comportement du script.
- Vous constaterez que le programme s'interrompt car la variable nombre n'est pas définie.
- Pour éviter ce problème, utilisez les blocs try et catch afin de gérer l'erreur sans interrompre l'exécution du programme. Assurez-vous de récupérer le message d'erreur dans le bloc catch et de l'afficher dans la console.
- Observez le comportement du script dans la console de votre navigateur. Si vous avez correctement intégré les blocs try et catch, le message Fin du traitement! devrait s'afficher, même en présence d'une erreur provoquée par Math.sqrt(nombre).
Exo-gestion-des-erreurs-02
- Objectif : Créer une fonction qui valide une donnée et qui, en cas d'invalidité, lève volontairement une exception.
- Instructions :
- Dupliquez le dossier du projet précédent et renommez le exo-gestion-des-erreurs-02.
- Remplacez le code présent dans le fichier app.js par celui-ci :
function calculerRacineCarree(nombre) { return Math.sqrt(nombre); } const a = 9; const b = -4; const aRacineCarree = calculerRacineCarree(a); const bRacineCarree = calculerRacineCarree(b); console.log(`La racine carrée de ${a} vaut ${aRacineCarree}`); console.log(`La racine carrée de ${b} vaut ${bRacineCarree}`); console.log(`La somme des racines carrées de ${a} et ${b} est égale à ${aRacineCarree + bRacineCarree}`); console.log("Fin du traitement!"); - Ouvrez l'application dans votre navigateur et observez la console pour analyser le comportement du script.
- On constate que la racine carrée d'une valeur négative ne génère pas d'erreur, mais retourne NaN (Not a Number). Ce comportement peut poser problème, car si la suite du programme repose sur une série de calculs utilisant cette valeur, on risque d'obtenir un résultat inattendu en fin d'exécution, sans identifier précisément à quel moment le script a cessé de fonctionner comme prévu.
- Pour éviter ce type de problème, il est recommandé de vérifier la valeur passée à Math.sqrt() avant d'effectuer le calcul. Vous pouvez utiliser une condition pour vous assurer que la valeur est positive et lever une exception explicite à l'aide de throw new Error("Les valeurs négatives ne sont pas autoriées!") si ce n'est pas le cas.
- N'oubliez pas d'intégrer les blocs try et catch au bon endroit afin d'empêcher que la levée d'une exception n'interrompe l'exécution du programme.
- Observez le comportement du script dans la console du navigateur. Si le système de gestion des erreurs est correctement configuré, vous devriez voir apparaître le message d'erreur que vous avez configuré, suivi du message Fin du traitement!.
- Remplacez la valeur de b par un nombre positif, puis observez à nouveau le comportement du script dans la console du navigateur. Si tout fonctionne comme prévu, vous devriez voir s'afficher les racines carrées des deux nombres, leur somme, ainsi que le message Fin du traitement!.
Exo-gestion-des-erreurs-03
- Objectif : Centraliser la gestion des erreurs.
- Instructions :
- Dupliquez le dossier du projet précédent et renommez-le exo-gestion-des-erreurs-03.
- Ajoutez une nouvelle fonction nommée verifierNombrePositif() au début du fichier app.js.
- La fonction verifierNombrePositif() doit prendre une valeur en entrée et effectuer deux vérifications :
- 3.1 Vérifier que la valeur est un nombre. Si ce n'est pas le cas, lever une exception avec throw new Error(), en affichant le message suivant : La valeur doit être un nombre!.
- 3.2 Si la valeur est bien un nombre, vérifier qu'elle est positive. Si elle est négative, lever une exception avec throw new Error() et le message : Le nombre doit être positif!.
- Si la valeur passée est un nombre positif, la fonction retourne ce nombre.
- Modifiez la fonction calculerRacineCarree() afin qu'elle délègue la vérification du nombre à la fonction estUnNombrePositif(). Celle-ci devra s'assurer que la valeur passée en argument est bien un nombre positif avant de poursuivre le calcul.
- Si la vérification réalisée avec verifierNombrePositif() n'a levé aucune exception, la fonction calculerRacineCarree() pourra alors retourner la racine carrée du nombre.
- Observez le comportement du script dans la console du navigateur. Si le système de gestion des erreurs est correctement configuré, vous devriez voir apparaître le message d'erreur Le nombre doit être positif!, suivi du message Fin du traitement!.
- Remplacez la valeur de b par une valeur qui n'est pas un nombre, puis observez à nouveau le comportement du script dans la console du navigateur. Si tout fonctionne comme prévu, le message d'erreur La valeur doit être un nombre! devrait s'afficher, suivi du message Fin du traitement!.
- Enfin, remplacez la valeur de b par un nombre positif, puis observez à nouveau le comportement du script dans la console du navigateur. Si tout fonctionne comme prévu, vous devriez voir apparaître les racines carrées des deux nombres, leur somme, ainsi que le message Fin du traitement!.
Exo-gestion-des-erreurs-04
- Objectif : Lever des exceptions avec des erreurs spécialisées et gérer ces erreurs en fonction de leur nature.
- Instructions :
- Dupliquez le dossier du projet précédent et renommez-le exo-gestion-des-erreurs-04.
- Modifiez la fonction verifierNombrePositif() afin de remplacer les exceptions de type générique Error par des exceptions plus spécifiques et adaptées à chaque cas :
- Si la valeur passée n'est pas un nombre, levez une exception de type TypeError.
- Si la valeur est un nombre négatif, levez une exception de type RangeError.
- Modifiez le bloc catch pour qu'il identifie le type d'erreur et affiche un message correspondant. Utilisez instanceof pour comparer l'erreur capturée et la classer selon son type :
- Erreur de type : suivi du message d'erreur, pour les exceptions de type TypeError.
- Erreur de plage de valeurs : suivi du message d'erreur, pour les exceptions de type RangeError.
- Erreur inconnue : suivi du message d'erreur, pour les exceptions qui ne correspondent à aucun des types spécifiés.
- Pour vérifier que le système de gestion des erreurs fonctionne correctement, observez son comportement dans la console du navigateur. Comme dans l'exercice précédent, testez différents cas en modifiant la valeur de b. D'abord, avec la valeur d'origine (un nombre négatif). Ensuite, avec une valeur qui n'est pas un nombre. Enfin, avec un nombre positif.
- Finalement, plutôt que d'appeler l'affichage du message Fin du traitement! dans l'espace global, il est préférable de le placer dans un bloc finally. Cela garantit son affichage systématique, qu'une erreur soit levée ou non, et permet de centraliser les actions de fin de traitement pour un code plus clair et maintenable.
- Répétez l'observation dans la console du navigateur, d'abord avec une valeur b invalide, puis avec une valeur valide, afin de vous assurer que le message s'affiche systématiquement, quel que soit le cas.