Trop souvent mise de côté, la gestion des erreurs en JavaScript est pourtant essentielle. Tâche ennuyante, beaucoup de développeurs freelances ne traitent que peu, voire pas du tout, les erreurs de ce langage script. Cette gestion est pourtant obligatoire pour avoir une bonne expérience utilisateur, et donc, indirectement, un bon taux de rétention.
Vous voulez tout savoir sur la gestion des erreurs en JavaScript ? Faisons le point !
D’où viennent les erreurs en JavaScript ?
Pour savoir comment gérer les erreurs en JavaScript, il est important d’en connaître l’origine. Et elle peut être multiple ! Faisons une liste rapide des différentes sources d’erreurs avec ce langage front :
- l’erreur peut provenir du navigateur (API JavaScript non supporté) ;
- elle peut venir du serveur (erreur serveur 4xx ou 5xx, ou données retournées qui ne sont pas dans le format attendu par le front) ;
- d’une mauvaise gestion du code par le développeur : conditions mal écrites, erreurs de syntaxe, etc. ;
- elle peut venir de l’utilisateur de la page web, en cas d’envoi de valeur non valide dans un formulaire par exemple (mais le traitement de ces erreurs est également à la charge du développeur).
Ces origines sont donc multiples, il convient alors de faire attention à chaque blocs de code, en se demandant si une erreur pourrait y survenir, quelle qu’en soit l’origine.
Pourquoi gérer les erreurs ?
Bien que nous connaissions maintenant les origines de ces erreurs, se pose toujours la question : pourquoi les gérer ?
En effet, si certains appels serveur n’aboutissent pas, cela ne veut pas dire que le site va planter pour autant.
Le problème est qu’on ne connaît jamais exactement les conséquences d’une erreur, aussi petite soit-elle.
En JavaScript, une erreur dans le code est susceptible d’arrêter le script complètement, et donc de bloquer le site ; on ne peut pas faire pire comme expérience utilisateur !
En parlant d’utilisateur, si une erreur surgit et qu’elle le concerne, même si elle n’est pas bloquante, il faut la gérer, et surtout le lui dire. Par exemple, si l’internaute a mal rempli un champ dans un formulaire et que cela crée une erreur, il faut pouvoir la lui expliquer clairement, pour qu’il puisse la corriger.
Les différents types d’erreurs JavaScript
Lorsqu’une erreur JavaScript se produit, qu’elle soit gérée ou non, elle est nativement définie par plusieurs éléments. Parmi ces éléments, il y a le type.
Ce type fait partie d’une liste finie, dont voici les différents éléments :
- RangeError : cette erreur apparaît lorsqu’un nombre est en dehors de sa plage de valeurs. Par exemple, lorsque l’on essaye de créer un tableau JavaScript avec plus d’éléments que ce que le langage ne permet ;
- ReferenceError : erreur plus commune, la ReferenceError survient lorsque l’on essaye de faire appel à une variable, mais que celle-ci n’est pas atteignable. Cela peut être le cas lors d’une erreur de frappe dans le nom de la variable, ou lors d’un appel, si la variable n’est pas encore déclarée ;
- SyntaxError : la SyntaxError est sans doute le type le plus commun, elle surgit lorsque l’erreur vient de la syntaxe du code : oubli d’accolades ou de parenthèses, oubli de virgule entre les différents éléments d’un tableau, etc. ;
- TypeError : bien que JavaScript ne soit pas un langage typé, essayer, par exemple, d’utiliser des fonctions propres à une string (’toLowerCase()’, typiquement), sur une variable qui contient un nombre, renverra cette erreur ;
- URIError : JavaScript renvoie cette erreur lorsque l’on appelle une méthode native liée à l’encodage ou le décodage d’URI (Uniform Resource Indicator), en passant en paramètre une valeur qui n’est pas de type URI ;
- EvalError : erreur levée lorsqu’un problème surgit à l’utilisation de la fonction native eval(). Ce type d’erreur n’existe plus, selon les spécifications ECMAScript, mais JavaScript la déclenche toujours pour en assurer la rétrocompatibilité ;
- InternalError : enfin, InternalError est une erreur qui survient, en général, lorsque le moteur JavaScript “surchauffe”, à cause d’un traitement trop lourd de code (typiquement, une boucle infinie).
Il est bon de savoir que certaines de ces erreurs sont repérables avant le runtime (lancement du script), par exemple grâce à l’utilisation d’un linter, type ESLint.
Si vous voulez en savoir plus sur les différents types natifs d’erreurs JavaScript, rendez-vous sur la documentation Mozilla.
Les différentes façons de gérer des erreurs en JavaScript
L’objet Error
Avant de lister les différentes façons de supporter les erreurs en JavaScript, il est bon de parler de l’élément natif du langage qui permet d’en améliorer la gestion : l’objet Error.
Error vient avec plusieurs propriétés, dont deux essentielles :
- name : le type d’erreur, qui correspond, de base, aux types définis dans la section précédente (SyntaxError, InternalError, etc.)
- message : la chaîne de caractères associée à l’erreur, souvent ce que l’on affiche à l’écran pour l’utilisateur.
Lorsque l’on veut capter une erreur JavaScript, on utilise donc l’objet Error, associé au mot-clé ‘throw’. On lance littéralement l’erreur :
throw new Error("L'erreur X vient de survenir")
Ici, lorsqu’on captera l’erreur, l’attribut ‘message’ de l’objet aura pour valeur : « L’erreur X vient de survenir ».
Il est également possible de personnaliser l’objet Error. Le moyen le plus propre de faire cela, est de créer une classe qui étend Error :
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
throw new ValidationError("Propriété non trouvée : Nom")
Ici, on personnalise à la fois le message et le name (type).
Les blocs try catch
On sait maintenant comment renvoyer une erreur… mais encore faut-il la capter !
Cela se fait notamment via le fameux bloc try…catch. Vous le connaissez sûrement, mais rappelons son fonctionnement :
- dans le bloc ‘try’ sera mis le code que l’on veut exécuter, qui est susceptible de déclencher une erreur ;
- dans le bloc ‘catch’ viendra le code parcouru si jamais une erreur survient dans le programme contenu dans le ‘try’.
Voyons un exemple de cette utilisation, en reprenant l’erreur ValidationError définie plus haut :
function validateForm() {
if(form.name == null || form.name == undefined || form.name == ""){
throw new ValidationError("Propriété non trouvée : Nom");
}
}
try {
validateForm();
} catch (e) {
if (e instanceof ValidationError)
this.errorMessage = e.message
else
this.errorMessage = "Une erreur inconnue est survenue";
}
Ici, dans le bloc try, on vient exécuter le code normal : la vérification du formulaire HTML. Dans celle-ci, si la propriété ‘name’ est invalide, on fait appel à la surcharge de l’Error définie dans l’exemple précédent.
Le throw Error vient casser l’enchaînement de code dans le try, pour prendre le relais dans le catch. Dans ce catch, le paramètre ‘e’ est envoyé, qui correspond à l’Error native JavaScript.
Cette dernière aura donc pour name ‘ValidationError’ et pour message ‘Propriété non trouvée : Nom’.
Le try…catch fait partie des outils classiques du développeur JavaScript. Attention néanmoins, son utilisation peut compliquer la lecture du code.
Le bloc finally du try…catch
Le try…catch vous semble déjà assez complexe ? Attendez la suite !
En fait, il existe une troisième propriété, en plus du try et du catch. Il s’agit du finally.
L’utilité du finally, est qu’il sera exécuté quoi qu’il arrive. Que tout le code se déroule sans encombre, ou qu’une erreur soit levée, ce que contient le finally sera parcouru.
Car, rappelons-le, si une erreur survient dans le try, le reste du code ne s’exécute pas : c’est le catch qui prend le relais. Or, on a parfois besoin qu’un morceau de code précis soit toujours pris en compte : on utilisera donc le finally.
La méthode onerror, pour capturer les erreurs HTML
La méthode onerror est appelable sur tous les éléments du DOM HTML, et permet de détecter les erreurs qui surviendraient directement sur le HTML.
Son utilisation est donc limitée, avec les frameworks JavaScript actuels, mais cette méthode peut toujours trouver son utilité, par exemple pour le chargement des images. On pourrait ainsi détecter une erreur sur l’URL fournie dans la balise :
const image = document.querySelector("img")
image.onerror = (event) => {
console.log("Error occurred: " + event)
}
Ainsi, nous pouvons traiter l’erreur à sa source, sans avoir besoin de faire appel à des try…catch.
Les promesses JavaScript
Les promesses (ou promises), sont aujourd’hui très utilisées, notamment appréciées pour leur fonctionnement asynchrone, nativement absent de JavaScript.
Ce que les développeurs apprécient également avec les promesses, c’est la possibilité d’avoir deux types de retours, un en cas de succès (then), l’autre en cas d’échec (catch). Ce qui permet une gestion des erreurs, et d’éviter ainsi la fin forcée du script en cours.
Voici une utilisation classique des promesses :
const myFunc = async () => {
return new Promise ((resolve, reject) => {
if(true){ // condition respectée en cas de réussite (d'un appel serveur, par exemple)
resolve (true);
}
else { // else, si la condition de réussite échoue
reject (false);
}
}
}
myFunc().then(() => {
// retour depuis le resolve
}).catch(() => {
// retour depuis le reject
}
Rassembler la gestion des erreurs JavaScript
Un conseil que nous pouvons vous donner pour conclure : il est conseillé de rassembler la gestion des erreurs au même endroit, dans le code.
C’est-à-dire, pour le front, de créer un service qui va gérer toutes les erreurs. Il sera appelé dans les catchs, pour pouvoir retourner des messages d’erreurs compréhensibles par l’utilisateur, ou pour alerter les développeurs en cas d’erreur critique.
Côté back-end, avec Node, il est possible d’avoir un middleware pour traiter toutes les erreurs, cela permet de rassembler toute cette gestion en un même point d’entrée, et rendre le code plus propre et maintenable.