Une fonction qui reçoit une donnée qu'elle ne sait pas traiter a deux choix : renvoyer une valeur bancale et laisser le problème se propager, ou refuser franchement. throw est le mot-clé du refus franc.
Bien employé, il transforme une panne lointaine et incompréhensible en un message précis, levé à l'endroit exact où la donnée est entrée dans le programme.
Définition
throw lève une erreur. La fonction courante s'arrête immédiatement, et le moteur remonte la pile d'appels à la recherche du premier catch disponible. S'il n'en trouve aucun, l'erreur s'affiche dans la console et le processus s'arrête.
function diviser(a, b) {
if (b === 0) {
throw new Error("Division par zéro");
}
return a / b;
}
try {
diviser(10, 0);
} catch (erreur) {
console.log(erreur.name, ":", erreur.message);
// Error : Division par zéro
}Notez le new devant Error : on ne lève pas un message, on lève un objet construit pour l'occasion.
Lever un objet Error, jamais une chaîne
Le langage accepte n'importe quelle valeur : un nombre, une chaîne, un objet quelconque. Une liberté qui coûte cher, et le tableau suivant explique pourquoi.
| Ce qui est levé | Ce que le bloc de rattrapage obtient |
|---|---|
| Une chaîne | Aucune pile d'appels, aucun type, un message nu |
| Un objet ordinaire | Des propriétés utiles, mais toujours aucune pile |
| Une instance de Error | Type, message, pile d'appels et cause éventuelle |
La pile d'appels est la partie irremplaçable : c'est elle qui indique la ligne fautive et le chemin parcouru pour y arriver.
Ses propres types d'erreur
Dès qu'une application distingue plusieurs familles de problèmes, il devient utile de créer ses propres types en héritant de Error. Le rattrapage peut alors trier avec instanceof au lieu de comparer des messages, qui changent au premier remaniement.
class ErreurDePaiement extends Error {
constructor(message, code) {
super(message);
this.name = "ErreurDePaiement";
this.code = code;
}
}
try {
throw new ErreurDePaiement("Carte refusée", "card_declined");
} catch (erreur) {
console.log(erreur instanceof Error); // true
console.log(erreur.name, erreur.code); // ErreurDePaiement card_declined
}La construction repose sur class, extends et super, trois mots-clés qui suffisent à obtenir un type d'erreur complet. L'appel à super(message) est obligatoire : c'est lui qui renseigne le message et capture la pile.
Pour relancer une erreur en ajoutant du contexte sans perdre l'originale, passez-la en cause : throw new Error("Configuration illisible", { cause: erreur }). Le bloc du dessus retrouve la première erreur dans erreur.cause.
Questions fréquentes
Où faut-il lever, et où faut-il rattraper ?
Levez au plus près de la donnée fautive, là où le programme dispose du contexte pour dire ce qui ne va pas. Rattrapez au plus près de l'utilisateur, là où une décision est possible : afficher un message, réessayer, revenir à une valeur de repli.
Une erreur levée arrête-t-elle tout le programme ?
Non, elle arrête le fil d'exécution en cours jusqu'au premier bloc capable de la recevoir. Dans un navigateur, une erreur non rattrapée interrompt le gestionnaire d'événement concerné mais la page reste vivante. Dans Node.js, elle met fin au processus, ce qui explique pourquoi les serveurs entourent chaque requête d'un rattrapage.
Vaut-il mieux lever une erreur ou renvoyer null ?
Renvoyer null convient quand l'absence est un résultat normal, comme une recherche qui ne trouve rien. Levez une erreur quand la situation est anormale et qu'aucun appelant raisonnable ne peut continuer, comme une clé d'API manquante. Le critère n'est pas la gravité mais la question suivante : est-ce que l'appelant a quelque chose de sensé à faire de cette valeur.