La loi de Murphy stipule que tout ce qui peut aller mal finira par aller mal. Cette loi s’applique un peu trop bien dans le monde de la programmation. Si vous créez une application, il y a de fortes chances que vous créiez des bugs et autres problèmes. Les erreurs dans JavaScript sont l’un de ces problèmes courants !

Le succès d’un produit logiciel dépend de la capacité de ses créateurs à résoudre ces problèmes avant de nuire à leurs utilisateurs. Et JavaScript, de tous les langages de programmation, est réputé pour sa conception moyenne du traitement des erreurs.

Si vous construisez une application JavaScript, il y a de fortes chances que vous vous trompiez sur les types de données à un moment ou à un autre. Si ce n’est pas le cas, vous pourriez finir par remplacer un indéfini par un null ou un opérateur triple égal (===) par un opérateur double égal (==).

Il est tout à fait humain de faire des erreurs. C’est pourquoi nous allons vous montrer tout ce que vous devez savoir sur la gestion des erreurs dans JavaScript.

Cet article vous guidera à travers les erreurs de base dans JavaScript et expliquera les différentes erreurs que vous pouvez rencontrer. Vous apprendrez ensuite comment identifier et corriger ces erreurs. Vous trouverez également quelques conseils pour gérer efficacement les erreurs dans les environnements de production.

Sans plus attendre, commençons !

Consultez notre guide vidéo sur le traitement des erreurs JavaScript

Que sont les erreurs JavaScript ?

Les erreurs en programmation font référence à des situations qui ne permettent pas à un programme de fonctionner normalement. Cela peut se produire lorsqu’un programme ne sait pas comment gérer la tâche à accomplir, par exemple lorsqu’il essaie d’ouvrir un fichier inexistant ou de communiquer avec un point de terminaison API basé sur le web alors qu’il n’y a pas de connectivité réseau.

Ces situations poussent le programme à envoyer des erreurs à l’utilisateur, indiquant qu’il ne sait pas comment procéder. Le programme recueille autant d’informations que possible sur l’erreur et signale ensuite qu’il ne peut pas aller de l’avant.

Les programmeurs intelligents essaient de prévoir et de couvrir ces scénarios afin que l’utilisateur n’ait pas à comprendre un message d’erreur technique comme « 404 » de manière indépendante. Au lieu de cela, ils affichent un message beaucoup plus compréhensible : « La page n’a pas pu être trouvée »

Les erreurs en JavaScript sont des objets affichés à chaque fois qu’une erreur de programmation se produit. Ces objets contiennent de nombreuses informations sur le type d’erreur, l’instruction qui a provoqué l’erreur et la trace de la pile lorsque l’erreur s’est produite. JavaScript permet également aux programmeurs de créer des erreurs personnalisées pour fournir des informations supplémentaires lors du débogage des problèmes.

Propriétés d’une erreur

Maintenant que la définition d’une erreur JavaScript est claire, il est temps de se plonger dans les détails.

Les erreurs en JavaScript comportent certaines propriétés standard et personnalisées qui aident à comprendre la cause et les effets de l’erreur. Par défaut, les erreurs en JavaScript contiennent trois propriétés :

  1. message : Une valeur de type chaîne qui porte le message d’erreur
  2. name : Le type d’erreur qui s’est produit (Nous y reviendrons en détail dans la prochaine section)
  3. stack : La trace de la pile du code exécuté lorsque l’erreur s’est produite.

En outre, les erreurs peuvent également comporter des propriétés telles que columnNumber, lineNumber, fileName, etc. pour mieux décrire l’erreur. Toutefois, ces propriétés ne sont pas standard et peuvent ou non être présentes dans chaque objet d’erreur généré par votre application JavaScript.

Comprendre la trace de la pile

Une trace de pile est la liste des appels de méthode dans lesquels se trouvait un programme lorsqu’un événement tel qu’une exception ou un avertissement s’est produit. Voici à quoi ressemble un exemple de suivi de pile accompagné d’une exception :

Exemple de trace de pile.
Exemple de trace de pile.

Comme vous pouvez le voir, elle commence par afficher le nom et le message d’erreur, suivis d’une liste des méthodes qui ont été appelées. Chaque appel de méthode indique l’emplacement de son code source et la ligne à laquelle il a été invoqué. Vous pouvez utiliser ces données pour naviguer dans votre base de code et identifier le morceau de code à l’origine de l’erreur.

Cette liste de méthodes est disposée de manière empilée. Elle montre où votre exception a été levée en premier et comment elle s’est propagée à travers les appels de méthodes empilés. L’implémentation d’un catch pour l’exception ne la laissera pas se propager dans la pile et faire planter votre programme. Cependant, vous pourriez vouloir laisser des erreurs fatales non attrapées pour faire planter le programme dans certains scénarios intentionnels.

Erreurs vs exceptions

La plupart des gens considèrent généralement les erreurs et les exceptions comme une seule et même chose. Cependant, il est essentiel de noter une différence légère mais fondamentale entre elles.

Pour mieux comprendre cela, prenons un exemple rapide. Voici comment vous pouvez définir une erreur en JavaScript :

const wrongTypeError = TypeError("Wrong type found, expected character")

Et voici comment l’objet wrongTypeError devient une exception :

throw wrongTypeError

Cependant, la plupart des gens ont tendance à utiliser la forme abrégée qui définit les objets d’erreur tout en les lançant :

throw TypeError("Wrong type found, expected character")

C’est une pratique standard. Cependant, c’est l’une des raisons pour lesquelles les développeurs ont tendance à confondre exceptions et erreurs. Par conséquent, il est essentiel de connaître les principes de base, même si vous utilisez des raccourcis pour effectuer votre travail rapidement.

Types d’erreurs en JavaScript

Il existe une série de types d’erreurs prédéfinis en JavaScript. Ils sont automatiquement choisis et définis par le runtime JavaScript lorsque le programmeur ne gère pas explicitement les erreurs dans l’application.

Cette section vous permettra de parcourir certains des types d’erreurs les plus courants en JavaScript et de comprendre quand et pourquoi ils se produisent.

RangeError

Une RangeError est déclenchée lorsqu’une variable est définie avec une valeur en dehors de sa plage de valeurs légales. Cela se produit généralement lorsqu’on passe une valeur comme argument à une fonction et que la valeur donnée ne se trouve pas dans la plage des paramètres de la fonction. Il peut parfois être difficile à corriger lors de l’utilisation de bibliothèques tierces mal documentées, car vous devez connaître la plage des valeurs possibles pour les arguments afin de transmettre la valeur correcte.

Voici quelques-uns des scénarios courants dans lesquels RangeError se produit :

  • Essayer de créer un tableau de longueurs illégales via le constructeur Array.
  • Passer de mauvaises valeurs aux méthodes numériques comme toExponential(), toPrecision(), toFixed(), etc.
  • Transmettre des valeurs illégales à des fonctions de chaîne de caractères comme normalize().

ReferenceError

Une ReferenceError se produit lorsque quelque chose ne va pas avec la référence d’une variable dans votre code. Vous avez peut-être oublié de définir une valeur pour la variable avant de l’utiliser, ou vous essayez peut-être d’utiliser une variable inaccessible dans votre code. Dans tous les cas, l’examen de la trace de la pile fournit suffisamment d’informations pour trouver et corriger la référence de la variable en cause.

Voici quelques-unes des raisons courantes pour lesquelles les ReferenceErrors se produisent :

  • Faire une faute de frappe dans un nom de variable.
  • Essayer d’accéder à des variables de type bloc en dehors de leur champ d’application.
  • Faire référence à une variable globale d’une bibliothèque externe (comme $ de jQuery) avant qu’elle ne soit chargée.

SyntaxError

Ces erreurs sont parmi les plus simples à corriger car elles indiquent une erreur dans la syntaxe du code. JavaScript étant un langage de script interprété plutôt que compilé, ces erreurs se produisent lorsque l’application exécute le script qui contient l’erreur. Dans le cas des langages compilés, ces erreurs sont identifiées lors de la compilation. Ainsi, les binaires de l’application ne sont pas créés tant qu’elles ne sont pas corrigées.

Voici quelques-unes des raisons courantes pour lesquelles les SyntaxErrors peuvent se produire :

  • Virgules inversées manquantes
  • Parenthèses fermantes manquantes
  • Mauvais alignement des accolades ou d’autres caractères

Une bonne pratique consiste à utiliser un outil de linting dans votre IDE pour identifier ces erreurs avant qu’elles n’apparaissent dans le navigateur.

TypeError

TypeError est l’une des erreurs les plus courantes dans les applications JavaScript. Cette erreur est créée lorsqu’une valeur ne s’avère pas être d’un type particulier attendu. Voici quelques-uns des cas les plus courants où elle se produit :

  • Invoquer des objets qui ne sont pas des méthodes.
  • Tentative d’accès à des propriétés d’objets nuls ou indéfinis
  • Traiter une chaîne de caractères comme un nombre ou vice versa

Il y a beaucoup plus de possibilités où une TypeError peut se produire. Nous examinerons plus tard quelques cas célèbres et apprendrons comment les corriger.

InternalError

Le type InternalError est utilisé lorsqu’une exception se produit dans le moteur d’exécution JavaScript. Elle peut indiquer ou non un problème avec votre code.

Le plus souvent, InternalError ne se produit que dans deux scénarios :

  • Lorsqu’un patch ou une mise à jour du runtime JavaScript comporte un bogue qui lève des exceptions (cela se produit très rarement)
  • Lorsque votre code contient des entités trop volumineuses pour le moteur JavaScript (par exemple, trop de cas de commutation, initialisations de tableaux trop importantes, trop de récursion)

L’approche la plus appropriée pour résoudre cette erreur est d’identifier la cause via le message d’erreur et de restructurer la logique de votre application, si possible, pour éliminer le pic soudain de charge de travail sur le moteur JavaScript.

URIError

L’erreur URIError se produit lorsqu’une fonction globale de traitement des URI, telle que decodeURIComponent, est utilisée illégalement. Elle indique généralement que le paramètre transmis à l’appel de méthode n’était pas conforme aux normes URI et n’a donc pas été analysé correctement par la méthode.

Le diagnostic de ces erreurs est généralement facile, car il suffit d’examiner les arguments pour détecter les malformations.

EvalError

Une EvalError se produit lorsqu’une erreur se produit avec un appel de fonction eval(). La fonction eval() est utilisée pour exécuter du code JavaScript stocké dans des chaînes de caractères. Toutefois, étant donné que l’utilisation de la fonction eval() est fortement déconseillée en raison de problèmes de sécurité et que les spécifications ECMAScript actuelles ne lancent plus la classe EvalError, ce type d’erreur existe simplement pour maintenir la rétrocompatibilité avec l’ancien code JavaScript.

Si vous travaillez sur une ancienne version de JavaScript, vous pourriez rencontrer cette erreur. Dans tous les cas, il est préférable d’examiner le code exécuté dans l’appel de la fonction eval() pour détecter d’éventuelles exceptions.

Création de types d’erreur personnalisés

Bien que JavaScript offre une liste adéquate de classes de types d’erreurs pour couvrir la plupart des scénarios, vous pouvez toujours créer un nouveau type d’erreur si la liste ne répond pas à vos besoins. Le fondement de cette flexibilité réside dans le fait que JavaScript vous permet de lancer littéralement n’importe quoi avec la commande throw.

Donc, techniquement, ces déclarations sont tout à fait légales :

throw 8
throw "An error occurred"

Cependant, le lancement d’un type de données primitif ne fournit pas de détails sur l’erreur, comme son type, son nom ou la trace de la pile qui l’accompagne. Pour corriger cela et normaliser le processus de traitement des erreurs, la classe Error a été fournie. Il est également déconseillé d’utiliser des types de données primitifs pour lancer des exceptions.

Vous pouvez étendre la classe Error pour créer votre classe d’erreur personnalisée. Voici un exemple de base de la façon dont vous pouvez le faire :

class ValidationError extends Error {
    constructor(message) {
        super(message);
        this.name = "ValidationError";
    }
}

Et vous pouvez l’utiliser de la manière suivante :

throw ValidationError("Property not found: name")

Et vous pouvez ensuite l’identifier en utilisant le mot-clé instanceof:

try {
    validateForm() // code that throws a ValidationError
} catch (e) {
    if (e instanceof ValidationError)
    // do something
    else
    // do something else
}

Les 10 erreurs les plus courantes en JavaScript

Maintenant que vous avez compris les types d’erreur courants et comment créer vos propres types d’erreur, il est temps d’examiner les erreurs les plus courantes auxquelles vous serez confronté lors de l’écriture de code JavaScript.

Consultez notre guide vidéo sur les erreurs JavaScript les plus courantes

1. Uncaught RangeError

Cette erreur se produit dans Google Chrome dans plusieurs scénarios différents. Premièrement, elle peut se produire si vous appelez une fonction récursive et qu’elle ne se termine pas. Vous pouvez le vérifier vous-même dans la Chrome Developer Console :

Exemple de RangeError avec un appel de fonction récursive.
Exemple de RangeError avec un appel de fonction récursive.

Donc, pour résoudre une telle erreur, assurez-vous de définir correctement les cas limites de votre fonction récursive. Une autre raison pour laquelle cette erreur se produit est que vous avez transmis une valeur qui est hors de la plage des paramètres d’une fonction. Voici un exemple :

Exemple de RangeError avec appel à toExponential().
Exemple de RangeError avec appel à toExponential().

Le message d’erreur indique généralement ce qui ne va pas dans votre code. Une fois que vous aurez apporté les modifications nécessaires, le problème sera résolu.

Sortie pour l'appel de la fonction toExponential().
Sortie pour l’appel de la fonction toExponential().

2. Uncaught TypeError : Cannot set property

Cette erreur se produit lorsque vous définissez une propriété sur une référence non définie. Vous pouvez reproduire le problème avec ce code :

var list
list.count = 0

Voici la sortie que vous recevrez :

Exemple de TypeError.
Exemple de TypeError.

Pour corriger cette erreur, initialisez la référence avec une valeur avant d’accéder à ses propriétés. Voici à quoi cela ressemble une fois corrigé :

Comment corriger TypeError.
Comment corriger TypeError.

3. Uncaught TypeError : Cannot read property

Il s’agit de l’une des erreurs les plus fréquentes en JavaScript. Cette erreur se produit lorsque vous tentez de lire une propriété ou d’appeler une fonction sur un objet non défini. Vous pouvez la reproduire très facilement en exécutant le code suivant dans une console Chrome Developer :

var func
func.call()

Voici la sortie :

Exemple d'erreur de type avec une fonction non définie.
Exemple d’erreur de type avec une fonction non définie.

Un objet non défini est l’une des nombreuses causes possibles de cette erreur. Une autre cause importante de ce problème peut être une initialisation incorrecte de l’état lors du rendu de l’interface utilisateur. Voici un exemple concret tiré d’une application React :

import React, { useState, useEffect } from "react";

const CardsList = () => {

    const [state, setState] = useState();

    useEffect(() => {
        setTimeout(() => setState({ items: ["Card 1", "Card 2"] }), 2000);
    }, []);

    return (
        <>
            {state.items.map((item) => (
                <li key={item}>{item}</li>
            ))}
        </>
    );
};

export default CardsList;

L’application démarre avec un conteneur d’état vide et reçoit certains éléments après un délai de 2 secondes. Le délai est mis en place pour imiter un appel réseau. Même si votre réseau est super rapide, vous serez toujours confronté à un léger retard en raison duquel le composant s’affichera au moins une fois. Si vous essayez d’exécuter cette application, vous recevrez l’erreur suivante :

TypeError de trace de pile dans un navigateur.
TypeError de trace de pile dans un navigateur.

Cela est dû au fait qu’au moment du rendu, le conteneur d’état est indéfini ; il n’existe donc aucune propriété items sur celui-ci. Il est facile de corriger cette erreur. Il vous suffit de fournir une valeur initiale par défaut au conteneur d’état.

// ...
const [state, setState] = useState({items: []});
// ...

Maintenant, après le délai fixé, votre application affichera une sortie similaire :

Sortie du code.
Sortie du code.

La correction exacte dans votre code peut être différente, mais l’essentiel ici est de toujours initialiser correctement vos variables avant de les utiliser.

4. TypeError : ‘undefined’ is not an object

Cette erreur se produit dans Safari lorsque vous essayez d’accéder aux propriétés ou d’appeler une méthode sur un objet non défini. Vous pouvez exécuter le même code que ci-dessus pour reproduire vous-même l’erreur.

Exemple de TypeError avec une fonction indéfinie.
Exemple de TypeError avec une fonction indéfinie.

La solution à cette erreur est également la même – assurez-vous que vous avez initialisé vos variables correctement et qu’elles ne sont pas indéfinies lors de l’accès à une propriété ou à une méthode.

5. TypeError : null is not an object

Cette erreur est, une fois encore, similaire à la précédente. Elle se produit sur Safari, et la seule différence entre les deux erreurs est que celle-ci est déclenchée lorsque l’objet dont la propriété ou la méthode est accessible est null au lieu de undefined. Vous pouvez la reproduire en exécutant le morceau de code suivant :

var func = null

func.call()

Voici le résultat que vous obtiendrez :

Exemple de TypeError avec la fonction null.
Exemple de TypeError avec la fonction null.

Puisque null est une valeur explicitement définie à une variable et non assignée automatiquement par JavaScript. Cette erreur ne peut se produire que si vous essayez d’accéder à une variable que vous avez définie vous-même sur null. Vous devez donc revoir votre code et vérifier si la logique que vous avez écrite est correcte ou non.

6. TypeError : Cannot read property ‘length’

Cette erreur se produit dans Chrome lorsque vous essayez de lire la longueur d’un objet null ou undefined. La cause de ce problème est similaire aux problèmes précédents, mais il se produit assez fréquemment lors de la manipulation de listes ; il mérite donc une mention spéciale. Voici comment vous pouvez reproduire le problème :

Exemple de TypeError avec un objet non défini.
Exemple de TypeError avec un objet non défini.

Cependant, dans les versions plus récentes de Chrome, cette erreur est signalée comme Uncaught TypeError: Cannot read properties of undefined. Voici comment cela se présente :

Exemple de TypeError avec un objet non défini sur les versions plus récentes de Chrome.
Exemple de TypeError avec un objet non défini sur les versions plus récentes de Chrome.

La solution, encore une fois, est de s’assurer que l’objet dont vous essayez d’accéder à la longueur existe et n’est pas défini sur null.

7. TypeError : ‘undefined’ is not a function

Cette erreur se produit lorsque vous essayez d’invoquer une méthode qui n’existe pas dans votre script, ou qui existe mais ne peut être référencée dans le contexte d’appel. Cette erreur se produit généralement dans Google Chrome, et vous pouvez la résoudre en vérifiant la ligne de code qui provoque l’erreur. Si vous trouvez une faute de frappe, corrigez-la et vérifiez si cela résout votre problème.

Si vous avez utilisé le mot-clé auto-référencé this dans votre code, cette erreur peut survenir si this n’est pas lié de manière appropriée à votre contexte. Considérez le code suivant :

function showAlert() {
    alert("message here")
}

document.addEventListener("click", () => {
    this.showAlert();
})

Si vous exécutez le code ci-dessus, il lancera l’erreur dont nous avons parlé. Cela se produit parce que la fonction anonyme passée en tant qu’écouteur d’événement est exécutée dans le contexte de document.

En revanche, la fonction showAlert est définie dans le contexte de window.

Pour résoudre ce problème, vous devez passer la référence appropriée à la fonction en la liant à la méthode bind():

document.addEventListener("click", this.showAlert.bind(this))

8. ReferenceError : event is not defined

Cette erreur se produit lorsque vous essayez d’accéder à une référence non définie dans la portée de l’appel. Cela se produit généralement lors de la manipulation d’événements puisqu’ils vous fournissent souvent une référence appelée event dans la fonction de rappel. Cette erreur peut se produire si vous oubliez de définir l’argument event dans les paramètres de votre fonction ou si vous l’orthographiez mal.

Cette erreur peut ne pas se produire dans Internet Explorer ou Google Chrome (car IE propose une variable d’événement globale et Chrome attache automatiquement la variable d’événement au gestionnaire), mais elle peut se produire dans Firefox. Il est donc conseillé de garder un œil sur ces petites erreurs.

9. TypeError : Assignment to constant variable

Il s’agit d’une erreur qui résulte d’un manque d’attention. Si vous essayez d’attribuer une nouvelle valeur à une variable constante, vous obtiendrez un tel résultat :

Exemple de TypeError avec affectation à un objet constant.
Exemple de TypeError avec affectation à un objet constant.

Bien que cela semble facile à corriger pour le moment, imaginez des centaines de déclarations de variables de ce type et l’une d’entre elles définie par erreur comme const au lieu de let! Contrairement à d’autres langages de script comme PHP, il y a une différence minime entre le style de déclaration des constantes et des variables en JavaScript. Il est donc conseillé de vérifier vos déclarations en premier lieu lorsque vous êtes confronté à cette erreur. Vous pouvez également rencontrer cette erreur si vous oubliez que ladite référence est une constante et que vous l’utilisez comme une variable. Cela indique soit une négligence, soit une faille dans la logique de votre application. Assurez-vous de le vérifier lorsque vous essayez de résoudre ce problème.

10. (unknow) : Script Error

Une erreur de script se produit lorsqu’un script tiers envoie une erreur à votre navigateur. Cette erreur est suivie de (iunknow) car le script tiers appartient à un domaine différent de celui de votre application. Le navigateur cache d’autres détails pour éviter la fuite d’informations sensibles du script tiers.

Vous ne pouvez pas résoudre cette erreur sans connaître tous les détails. Voici ce que vous pouvez faire pour obtenir plus d’informations sur l’erreur :

  1. Ajoutez l’attribut crossorigin dans la balise script.
  2. Définissez l’en-tête correct Access-Control-Allow-Origin sur le serveur qui héberge le script.
  3. [Facultatif] Si vous n’avez pas accès au serveur hébergeant le script, vous pouvez envisager d’utiliser un proxy pour relayer votre demande au serveur et la renvoyer au client avec les bons en-têtes.

Une fois que vous pouvez accéder aux détails de l’erreur, vous pouvez alors vous atteler à la résolution du problème, qui sera probablement lié soit à la bibliothèque tierce, soit au réseau.

Comment identifier et prévenir les erreurs en JavaScript

Si les erreurs évoquées ci-dessus sont les plus courantes et les plus fréquentes en JavaScript que vous rencontrerez, se fier à quelques exemples ne sera jamais suffisant. Il est vital de comprendre comment détecter et prévenir tout type d’erreur dans une application JavaScript tout en la développant. Voici comment vous pouvez gérer les erreurs en JavaScript.

Lancer et attraper manuellement les erreurs

La façon la plus fondamentale de traiter les erreurs qui ont été lancées manuellement ou par le runtime est de les attraper. Comme la plupart des autres langages, JavaScript propose un ensemble de mots-clés pour gérer les erreurs. Il est essentiel de connaître chacun d’entre eux en profondeur avant de vous lancer dans la gestion des erreurs dans votre application JavaScript.

throw

Le premier et le plus élémentaire des mots-clés de cet ensemble est throw. Comme il est évident, le mot-clé throw est utilisé pour lancer des erreurs afin de créer manuellement des exceptions dans le runtime JavaScript. Nous en avons déjà parlé plus tôt dans le document, et voici l’essentiel de la signification de ce mot-clé :

  • Vous pouvez throw tout, y compris les chiffres, les chaînes de caractères et les objets Error.
  • Toutefois, il est déconseillé de lancer des types de données primitifs tels que les chaînes de caractères et les nombres, car ils ne contiennent pas d’informations de débogage sur les erreurs.
  • Exemple throw TypeError("Please provide a string")

try

Le mot-clé try est utilisé pour indiquer qu’un bloc de code pourrait lancer une exception. Sa syntaxe est la suivante :

try {
    // error-prone code here
}

Il est important de noter qu’un bloc catch doit toujours suivre le bloc try pour gérer efficacement les erreurs.

catch

Le mot-clé catch est utilisé pour créer un bloc catch. Ce bloc de code est chargé de traiter les erreurs que le bloc try qui suit attrape. Voici sa syntaxe :

catch (exception) {
    // code to handle the exception here
}

Et voici comment vous implémentez ensemble les blocs try et catch :

try {
    // business logic code
} catch (exception) {
    // error handling code
}

Contrairement à C++ ou Java, vous ne pouvez pas ajouter plusieurs blocs catch à un bloc try en JavaScript. Cela signifie que vous ne pouvez pas faire ceci :

try {
    // business logic code
} catch (exception) {
    if (exception instanceof TypeError) {
        // do something
    }
} catch (exception) {
    if (exception instanceof RangeError) {
    // do something
    }
}

Au lieu de cela, vous pouvez utiliser une déclaration if...else ou une déclaration switch case à l’intérieur du bloc catch unique pour gérer tous les cas d’erreur possibles. Cela ressemblerait à ceci :

try {
    // business logic code
} catch (exception) {
    if (exception instanceof TypeError) {
        // do something
    } else if (exception instanceof RangeError) {
        // do something else
    }
}

finally

Le mot-clé finally est utilisé pour définir un bloc de code qui est exécuté après la gestion d’une erreur. Ce bloc est exécuté après les blocs try et catch.

En outre, le bloc finally sera exécuté quel que soit le résultat des deux autres blocs. Cela signifie que même si le bloc catch ne peut pas traiter entièrement l’erreur ou si une erreur est lancée dans le bloc catch, l’interpréteur exécutera le code dans le bloc finally avant que le programme ne s’écroule.

Pour être considéré comme valide, le bloc try en JavaScript doit être suivi d’un bloc catch ou finally. Sans l’un de ces éléments, l’interpréteur lèvera une SyntaxError. Par conséquent, veillez à faire suivre vos blocs try d’au moins l’un d’entre eux lorsque vous traitez les erreurs.

Traiter les erreurs de manière globale avec la méthode onerror()

La méthode onerror() est disponible pour tous les éléments HTML afin de gérer les erreurs qui peuvent survenir avec eux. Par exemple, si une balise img ne trouve pas l’image dont l’URL est spécifiée, elle déclenche sa méthode onerror pour permettre à l’utilisateur de gérer l’erreur.

En général, vous fournissez une autre URL d’image dans l’appel onerror pour que la balise img puisse se rabattre sur elle. Voici comment vous pouvez le faire via JavaScript :

const image = document.querySelector("img")

image.onerror = (event) => {
    console.log("Error occurred: " + event)
}

Cependant, vous pouvez utiliser cette fonctionnalité pour créer un mécanisme global de traitement des erreurs pour votre application. Voici comment vous pouvez le faire :

window.onerror = (event) => {
    console.log("Error occurred: " + event)
}

Grâce à ce gestionnaire d’événements, vous pouvez vous débarrasser des multiples blocs try...catch qui traînent dans votre code et centraliser le traitement des erreurs de votre application de manière similaire au traitement des événements. Vous pouvez attacher plusieurs gestionnaires d’erreurs à la fenêtre pour maintenir le principe de responsabilité unique des principes de conception SOLID. L’interpréteur passera en revue tous les gestionnaires jusqu’à ce qu’il atteigne celui qui convient.

Transmettre les erreurs via des callbacks

Si les fonctions simples et linéaires permettent à la gestion des erreurs de rester simple, les callbacks peuvent compliquer l’affaire.

Considérez le morceau de code suivant :

const calculateCube = (number, callback) => {
    setTimeout(() => {
        const cube = number * number * number
        callback(cube)
    }, 1000)
}

const callback = result => console.log(result)

calculateCube(4, callback)

La fonction ci-dessus démontre une condition asynchrone dans laquelle une fonction prend un certain temps pour traiter les opérations et renvoie le résultat plus tard à l’aide d’un callback.

Si vous essayez d’entrer une chaîne de caractères au lieu de 4 dans l’appel de la fonction, vous obtiendrez NaN comme résultat.

Cela doit être traité correctement. Voici comment :

const calculateCube = (number, callback) => {

    setTimeout(() => {
        if (typeof number !== "number")
            throw new Error("Numeric argument is expected")

        const cube = number * number * number
        callback(cube)
    }, 1000)
}

const callback = result => console.log(result)

try {
    calculateCube(4, callback)
} catch (e) { console.log(e) }

Dans l’idéal, cela devrait résoudre le problème. Cependant, si vous essayez de passer une chaîne de caractères à l’appel de fonction, vous obtiendrez ceci :

Exemple d'erreur avec le mauvais argument.
Exemple d’erreur avec le mauvais argument.

Bien que vous ayez mis en place un bloc try-catch lors de l’appel de la fonction, l’erreur n’est toujours pas attrapée. L’erreur est lancée après l’exécution du bloc catch en raison du délai d’attente.

Cela peut se produire rapidement dans les appels réseau, où des délais inattendus se glissent. Vous devez couvrir de tels cas lors du développement de votre application.

Voici comment vous pouvez gérer correctement les erreurs dans les callbacks :

const calculateCube = (number, callback) => {

    setTimeout(() => {
        if (typeof number !== "number") {
            callback(new TypeError("Numeric argument is expected"))
            return
        }
        const cube = number * number * number
        callback(null, cube)
    }, 2000)
}

const callback = (error, result) => {
    if (error !== null) {
        console.log(error)
        return
    }
    console.log(result)
}

try {
    calculateCube('hey', callback)
} catch (e) {
    console.log(e)
}

Maintenant, la sortie à la console sera :

Exemple de TypeError avec argument illégal.
Exemple de TypeError avec argument illégal.

Cela indique que l’erreur a été traitée de manière appropriée.

Traiter les erreurs dans Promises

La plupart des gens ont tendance à préférer les promises pour gérer les activités asynchrones. Elles présentent un autre avantage – une promise rejetée ne met pas fin à votre script. Cependant, vous devez toujours implémenter un bloc catch pour gérer les erreurs dans les promises. Pour mieux comprendre cela, réécrivons la fonction calculateCube() en utilisant des promises :

const delay = ms => new Promise(res => setTimeout(res, ms));

const calculateCube = async (number) => {
    if (typeof number !== "number")
        throw Error("Numeric argument is expected")
    await delay(5000)
    const cube = number * number * number
    return cube
}

try {
    calculateCube(4).then(r => console.log(r))
} catch (e) { console.log(e) }

Le timeout du code précédent a été isolé dans la fonction delay pour des raisons de compréhension. Si vous essayez d’entrer une chaîne de caractères au lieu de 4, la sortie que vous obtiendrez sera similaire à celle-ci :

Exemple de TypeError avec un argument illégal dans Promise.
Exemple de TypeError avec un argument illégal dans Promise.

Encore une fois, cela est dû au fait que le site Promise lance l’erreur après que tout le reste ait terminé son exécution. La solution à ce problème est simple. Il suffit d’ajouter un appel à catch() à la chaîne de promesses comme ceci :

calculateCube("hey")
.then(r => console.log(r))
.catch(e => console.log(e))

Maintenant, la sortie sera :

Exemple d'erreur de type traité avec un argument illégal.
Exemple d’erreur de type traité avec un argument illégal.

Vous pouvez observer à quel point il est facile de gérer les erreurs avec des promises. De plus, vous pouvez enchaîner un bloc finally() et l’appel de la promse pour ajouter du code qui s’exécutera une fois la gestion des erreurs terminée.

Vous pouvez également gérer les erreurs dans les promises en utilisant la technique traditionnelle try-catch-finally. Voici à quoi ressemblerait votre appel de promise dans ce cas :

try {
    let result = await calculateCube("hey")
    console.log(result)
} catch (e) {
    console.log(e)
} finally {
    console.log('Finally executed")
}

Cependant, cela ne fonctionne qu’à l’intérieur d’une fonction asynchrone. Par conséquent, la meilleure façon de gérer les erreurs dans les promises est d’enchaîner catch et finally à l’appel de promise.

throw/catch vs onerror() vs Callbacks vs Promises : Quelle est la meilleure solution ?

Avec quatre méthodes à votre disposition, vous devez savoir comment choisir la plus appropriée dans un cas d’utilisation donné. Voici comment vous pouvez décider par vous-même :

throw/catch

Vous utiliserez cette méthode la plupart du temps. Assurez-vous d’implémenter des conditions pour toutes les erreurs possibles dans votre bloc catch, et n’oubliez pas d’inclure un bloc finally si vous devez exécuter des routines de nettoyage de la mémoire après le bloc try.

Cependant, trop de blocs try/catch peuvent rendre votre code difficile à maintenir. Si vous vous trouvez dans une telle situation, il est préférable de gérer les erreurs via le gestionnaire global ou la méthode promise.

Lorsque vous devez choisir entre les blocs try/catch asynchrones et la méthode promise catch(), il est conseillé d’opter pour les blocs try/catch asynchrones car ils rendent votre code linéaire et facile à déboguer.

onerror()

Il est préférable d’utiliser la méthode onerror() lorsque vous savez que votre application doit gérer de nombreuses erreurs, et qu’elles peuvent être bien éparpillées dans la base de code. La méthode onerror vous permet de gérer les erreurs comme s’il s’agissait d’un autre événement traité par votre application. Vous pouvez définir plusieurs gestionnaires d’erreurs et les attacher à la fenêtre de votre application lors du rendu initial.

Toutefois, n’oubliez pas que la méthode onerror() peut s’avérer inutilement difficile à mettre en place dans les petits projets où le risque d’erreur est moindre. Si vous êtes sûr que votre application ne lancera pas trop d’erreurs, la méthode traditionnelle throw/catch vous conviendra mieux.

Callbacks et promises

Le traitement des erreurs dans les callbacks et les promises diffère en raison de la conception et de la structure de leur code. Toutefois, si vous devez choisir entre ces deux méthodes avant d’avoir écrit votre code, il est préférable d’opter pour les promises.

En effet, les promises ont une construction intégrée pour enchaîner un bloc catch() et un bloc finally() afin de gérer les erreurs facilement. Cette méthode est plus simple et plus propre que de définir des arguments supplémentaires/réutiliser des arguments existants pour gérer les erreurs.

Garder la trace des changements avec les dépôts Git

De nombreuses erreurs surviennent souvent en raison d’erreurs manuelles dans la base de code. Pendant le développement ou le débogage de votre code, vous pouvez finir par faire des changements inutiles qui peuvent provoquer l’apparition de nouvelles erreurs dans votre base de code. Les tests automatisés sont un excellent moyen de vérifier votre code après chaque modification. Cependant, il ne peut vous dire que si quelque chose ne va pas. Si vous ne faites pas de sauvegardes fréquentes de votre code, vous finirez par perdre du temps à essayer de réparer une fonction ou un script qui fonctionnait très bien auparavant.

C’est ici que git joue son rôle. Avec une stratégie de commit appropriée, vous pouvez utiliser votre historique git comme système de sauvegarde pour visualiser votre code tel qu’il a évolué au cours du développement. Vous pouvez facilement parcourir vos anciens commits et découvrir la version de la fonction qui fonctionnait bien avant, mais qui génère des erreurs après un changement sans rapport.

Vous pouvez alors restaurer l’ancien code ou comparer les deux versions pour déterminer ce qui a mal tourné. Les outils de développement web modernes comme GitHub Desktop ou GitKraken vous aident à visualiser ces changements côte à côte et à trouver rapidement les erreurs.

Une habitude qui peut vous aider à faire moins d’erreurs consiste à effectuer des revues de code chaque fois que vous apportez une modification importante à votre code. Si vous travaillez en équipe, vous pouvez créer une demande de modification et demander à un membre de l’équipe de la réviser en profondeur. Cela vous permettra d’utiliser une deuxième paire d’yeux pour repérer les erreurs qui vous auraient échappé.

Meilleures pratiques pour le traitement des erreurs en JavaScript

Les méthodes susmentionnées sont adéquates pour vous aider à concevoir une approche robuste de gestion des erreurs pour votre prochaine application JavaScript. Cependant, il serait préférable de garder quelques éléments à l’esprit lors de leur mise en œuvre pour tirer le meilleur parti de votre protection contre les erreurs. Voici quelques conseils pour vous aider.

1. Utilisez des erreurs personnalisées lorsque vous traitez des exceptions opérationnelles

Nous avons présenté les erreurs personnalisées au début de ce guide pour vous donner une idée de la façon dont vous pouvez personnaliser la gestion des erreurs en fonction du cas particulier de votre application. Il est conseillé d’utiliser des erreurs personnalisées dans la mesure du possible au lieu de la classe générique Error, car elle fournit plus d’informations contextuelles à l’environnement appelant sur l’erreur.

En outre, les erreurs personnalisées vous permettent de modérer la façon dont une erreur est affichée à l’environnement d’appel. Cela signifie que vous pouvez choisir de masquer des détails spécifiques ou d’afficher des informations supplémentaires sur l’erreur comme et quand vous le souhaitez.

Vous pouvez aller jusqu’à formater le contenu de l’erreur en fonction de vos besoins. Cela vous permet de mieux contrôler la façon dont l’erreur est interprétée et traitée.

2. N’acceptez aucune exception

Même les développeurs les plus expérimentés commettent souvent une erreur de débutant : ils consomment des niveaux d’exceptions au plus profond de leur code.

Vous pouvez rencontrer des situations où vous avez un morceau de code dont l’exécution est facultative. S’il fonctionne, tant mieux ; s’il ne fonctionne pas, vous n’avez pas besoin de faire quoi que ce soit.

Dans ces cas, il est souvent tentant de placer ce code dans un bloc try et d’y attacher un bloc catch vide. Cependant, en faisant cela, vous laissez ce morceau de code ouvert à la possibilité de provoquer n’importe quel type d’erreur et de s’en sortir. Cela peut devenir dangereux si vous avez une base de code importante et de nombreuses occurrences de ces constructions de mauvaise gestion des erreurs.

La meilleure façon de gérer les exceptions est de déterminer un niveau auquel elles seront toutes traitées et de les lever jusqu’à cet endroit. Ce niveau peut être un contrôleur (dans une application à architecture MVC) ou un intergiciel (dans une application traditionnelle orientée serveur).

De cette façon, vous saurez où trouver toutes les erreurs survenant dans votre application et choisir comment les résoudre, même si cela signifie ne rien faire.

3. Utilisez une stratégie centralisée pour les journaux et les alertes d’erreur

La journalisation d’une erreur fait souvent partie intégrante de son traitement. Ceux qui ne développent pas une stratégie centralisée pour la journalisation des erreurs peuvent passer à côté d’informations précieuses sur l’utilisation de leur application.

Les journaux d’événements d’une application peuvent vous aider à trouver des données cruciales sur les erreurs et à les déboguer rapidement. Si vous avez mis en place des mécanismes d’alerte appropriés dans votre application, vous pouvez savoir quand une erreur se produit dans votre application avant qu’elle n’atteigne une grande partie de votre base d’utilisateurs.

Il est conseillé d’utiliser un outil de journalisation pré-construit ou d’en créer un pour répondre à vos besoins. Vous pouvez configurer cet outil pour qu’il traite les erreurs en fonction de leur niveau (warning, debug, info, etc.), et certains outils de journalisation vont même jusqu’à envoyer immédiatement les journaux à des serveurs de journalisation distants. De cette façon, vous pouvez observer comment la logique de votre application se comporte avec des utilisateurs actifs.

4. Informez les utilisateurs des erreurs de manière appropriée

Un autre bon point à garder à l’esprit lors de la définition de votre stratégie de traitement des erreurs est de garder l’utilisateur à l’esprit.

Toutes les erreurs qui interfèrent avec le fonctionnement normal de votre application doivent présenter une alerte visible à l’utilisateur pour l’informer que quelque chose ne va pas, afin qu’il puisse essayer de trouver une solution. Si vous connaissez une solution rapide à l’erreur, comme réessayer une opération ou se déconnecter et se reconnecter, assurez-vous de le mentionner dans l’alerte pour aider à réparer l’expérience de l’utilisateur en temps réel.

Dans le cas d’erreurs qui n’interfèrent pas avec l’expérience quotidienne de l’utilisateur, vous pouvez envisager de supprimer l’alerte et de consigner l’erreur sur un serveur distant pour la résoudre ultérieurement.

5. Implémentez un middleware (Node.js)

L’environnement Node.js prend en charge les middlewares pour ajouter des fonctionnalités aux applications serveur. Vous pouvez utiliser cette fonctionnalité pour créer un intergiciel de gestion des erreurs pour votre serveur.

L’avantage le plus important de l’utilisation d’un middleware est que toutes vos erreurs sont traitées de manière centralisée en un seul endroit. Vous pouvez choisir d’activer/désactiver facilement cette configuration à des fins de test.

Voici comment vous pouvez créer un middleware :

const logError = err => {
    console.log("ERROR: " + String(err))
}

const errorLoggerMiddleware = (err, req, res, next) => {
    logError(err)
    next(err)
}

const returnErrorMiddleware = (err, req, res, next) => {
    res.status(err.statusCode || 500)
       .send(err.message)
}

module.exports = {
    logError,
    errorLoggerMiddleware,
    returnErrorMiddleware
}

Vous pouvez ensuite utiliser comme ceci ce middleware dans votre application :

const { errorLoggerMiddleware, returnErrorMiddleware } = require('./errorMiddleware')

app.use(errorLoggerMiddleware)

app.use(returnErrorMiddleware)

Vous pouvez maintenant définir une logique personnalisée à l’intérieur du middleware pour gérer les erreurs de manière appropriée. Vous n’avez plus à vous soucier de l’implémentation de constructions individuelles de gestion des erreurs dans l’ensemble de votre base de code.

6. Redémarrez votre application pour gérer les erreurs de programmeur (Node.js)

Lorsque les applications Node.js rencontrent des erreurs de programmeur, elles ne lèvent pas nécessairement une exception et tentent de fermer l’application. Ces erreurs peuvent inclure des problèmes résultant d’erreurs du programmeur, comme une consommation élevée du processeur, un gonflement de la mémoire ou des fuites de mémoire. La meilleure façon de gérer ces problèmes est de redémarrer l’application de manière élégante en la plantant via le mode cluster de Node.js ou un outil unique comme PM2. Cela permet de s’assurer que l’application ne se plante pas lors d’une action de l’utilisateur, ce qui présenterait une mauvaise expérience utilisateur.

7. Catch All Uncaught Exceptions (Node.js)

Vous ne pouvez jamais être sûr d’avoir couvert toutes les erreurs possibles qui peuvent se produire dans votre application. Par conséquent, il est essentiel de mettre en œuvre une stratégie de repli pour attraper toutes les exceptions non capturées de votre application.

Voici comment vous pouvez le faire :

process.on('uncaughtException', error => {
    console.log("ERROR: " + String(error))
    // other handling mechanisms
})

Vous pouvez également identifier si l’erreur qui s’est produite est une exception standard ou une erreur opérationnelle personnalisée. En fonction du résultat, vous pouvez quitter le processus et le redémarrer pour éviter tout comportement inattendu.

8. Attrapez tous les rejets de promesses non gérés (Node.js)

De la même manière que vous ne pouvez jamais couvrir toutes les exceptions possibles, il y a de fortes chances que vous ne puissiez pas traiter tous les rejets de promises possibles. Cependant, contrairement aux exceptions, les rejets de promises ne génèrent pas d’erreurs.

Ainsi, une promise importante qui a été rejetée peut passer pour un avertissement et laisser votre application ouverte à la possibilité d’un comportement inattendu. Il est donc crucial de mettre en place un mécanisme de secours pour gérer le rejet des promises.

Voici comment vous pouvez le faire :

const promiseRejectionCallback = error => {
    console.log("PROMISE REJECTED: " + String(error))
}

process.on('unhandledRejection', callback)

Résumé

Comme tout autre langage de programmation, les erreurs sont assez fréquentes et naturelles en JavaScript. Dans certains cas, vous pouvez même avoir besoin de lancer des erreurs intentionnellement pour indiquer la bonne réponse à vos utilisateurs. Par conséquent, il est très important de comprendre leur anatomie et leurs types.

En outre, vous devez être équipé des bons outils et techniques pour identifier et empêcher les erreurs de faire tomber votre application.

Dans la plupart des cas, une stratégie solide pour gérer les erreurs avec une exécution soignée est suffisante pour tous les types d’applications JavaScript.

Y a-t-il d’autres erreurs JavaScript que vous n’avez toujours pas réussi à résoudre ? Des techniques pour gérer les erreurs JS de manière constructive ? Faites-nous en part dans les commentaires ci-dessous !

Kumar Harsh

Kumar est un développeur de logiciels et un auteur technique basé en Inde. Il est spécialisé dans le JavaScript et le DevOps. Vous pouvez en apprendre plus sur son travail sur son site web.