Apprenez en lisant dans l'ordre

Lever des exceptions — throw et erreurs personnalisées

Lève une exception avec throw new Error, crée tes propres types avec extends Error et garde l'erreur d'origine avec cause.

Même si la page de commande envoie une quantité de 0, la fonction qui calcule le sous-total fait son calcul sans broncher, et une commande d'un montant nul passe. Afficher un avertissement n'arrêtera pas non plus l'appelant : il ne remarque pas le problème et enregistre la commande.

Cet article couvre throw new Error, qui te permet de lever toi-même une exception, et les erreurs personnalisées, des types d'erreur que tu définis toi-même.

S'arrêter sur une valeur invalide — throw new Error

Il faut interrompre le calcul du sous-total quand la quantité est un nombre décimal ou négatif. Le signaler avec return null ne suffit pas : si l'appelant oublie de vérifier, ajouter les frais de port avec null + 500 donne 500, et le calcul continue.

Un objet Error (une valeur dotée de name et de message, conçue pour être levée comme exception) se crée avec new Error("message"). Si tu le passes à throw, une exception est levée sur-le-champ ; si l'appel a eu lieu dans try, cette valeur arrive dans l'error de catch.

function calcSubtotal(unitPrice, quantity) {
  // Si ce n'est pas un entier supérieur ou égal à 1, lever une exception tout de suite
  if (!Number.isInteger(quantity) || quantity < 1) {
    // new Error seul n'arrête rien. Il faut le passer à throw pour en faire une exception
    throw new Error(`La quantité doit être un entier supérieur ou égal à 1 : ${quantity}`);
  }
  return unitPrice * quantity;
}

try {
  console.log(calcSubtotal(1200, 3));   // 3600
  console.log(calcSubtotal(1200, 0));   // lève une exception, donc rien ne s'affiche
  console.log("Commande confirmée");    // cette ligne ne s'exécute pas non plus
} catch (error) {
  console.log(error.name);              // Error
  console.log(error.message);           // La quantité doit être un entier supérieur ou égal à 1 : 0
}
Le code ne s'arrête que si tu passes l'erreur à throw
new Error seuldans le ifUn objet Errorest crééPas d'arrêt :ligne suivante1200 * 0renvoie 0throw new Errordans le ifUn objet Errorest crééException levéesur-le-champReçue par catchdans error
Les deux versions créent un objet Error. Ce n'est qu'une fois passé à throw que le code s'arrête sur-le-champ.

Place le if de validation en haut de la fonction, avant le calcul. Comme return, throw quitte la fonction sur-le-champ : le return unitPrice * quantity; qui suit ne s'exécute pas, et l'appelant ne reçoit jamais 0.

Vérifie la note de satisfaction avant d'enregistrer un questionnaire. scores est déjà déclaré.

① Si la note n'est pas un entier, lève une exception avec le message « La note doit être un entier ».

② Si elle est en dehors de 1 à 5, lève une exception avec le message « La note doit être comprise entre 1 et 5 ».

③ S'il n'y a pas de problème, renvoie-la sous la forme « Réponse envoyée avec la note 4 ».

④ Passe-lui chaque valeur de scores, continue même si l'une échoue, et affiche soit le résultat, soit le message d'erreur.

(Si tout s'exécute correctement, une explication apparaîtra.)

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Créer un type pour les erreurs de saisie — extends Error

Une exception levée avec new Error a toujours Error pour name, quelle que soit la vérification concernée. Un code postal avec le mauvais nombre de chiffres et un article en rupture de stock arrivent sous le même nom : le bloc catch ne peut donc pas savoir s'il s'agit d'un échec que l'utilisateur peut régler en corrigeant sa saisie.

Ajouter extends Error à une classe (la syntaxe qui définit un type d'objet que tu crées avec new) crée un type qui hérite du comportement d'Error. Si tu écris name = "ValidationError"; entre les accolades, les exceptions créées à partir de ce type auront cette chaîne comme name.

// Hériter d'Error pour créer un type dédié aux erreurs de saisie
class ValidationError extends Error {
  name = "ValidationError";   // devient le name des exceptions créées avec new
}

function checkZipCode(zipCode) {
  if (zipCode.length !== 5) {
    throw new ValidationError(`Le code postal doit comporter 5 chiffres : ${zipCode}`);
  }
  return zipCode;
}

try {
  checkZipCode("7500");
} catch (error) {
  console.log(error.name);               // ValidationError
  console.log(error.message);            // Le code postal doit comporter 5 chiffres : 7500
  console.log(error instanceof Error);   // true
}
La place des types créés avec extends Error
Error — les types d'exception dotés de message et de name
  • Pour une exception créée avec new Error("message") comme pour tous les types de ce cadre, instanceof Error vaut true
Types intégrés — fournis d'office par JavaScript
  • SyntaxError / TypeError / RangeError — les types vus dans l'article précédent
Types créés avec extends Error
  • ValidationError — représente une erreur de saisie
  • name vaut le "ValidationError" défini dans la classe
  • instanceof ValidationError ne vaut true que dans ce cadre
Les deux se trouvent dans le cadre Error, et aucun n'est inclus dans l'autre. instanceof ValidationError ne vaut true que dans son propre cadre.

Tu peux aussi passer une chaîne à throw, mais une chaîne n'a ni name ni message : lire error.message dans catch donne alors undefined. Le tableau ci-dessous montre ce que tu peux lire dans catch selon la valeur passée à throw.

Valeur passée à throwname lisible dans catchinstanceof Error
"Code postal invalide" (une chaîne)undefined (une chaîne n'en a pas)false
new Error("Code postal invalide")Errortrue
ValidationError avec name définiValidationErrortrue
ValidationError sans name définiError (hérité tel quel)true

Affiche pourquoi un fichier est refusé. QuotaError, files et checkFile sont déjà déclarés.

① Définis une FormatError qui hérite d'Error, et fixe son name.

② Dans checkFile, lève une FormatError avec le message « Seuls les PDF sont acceptés » si l'extension n'est pas pdf.

③ Vérifie chaque fichier de files et affiche soit la valeur renvoyée, soit « name: message ».

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Vérifier d'abord tes propres types — l'ordre des instanceof

Une erreur de saisie, l'utilisateur peut la corriger ; mais une exception inattendue comme une TypeError ne disparaîtra pas, quoi qu'il modifie dans sa saisie. Tu veux que catch affiche deux messages différents, mais si tu écris les vérifications dans le mauvais ordre, même les erreurs de saisie reçoivent le message prévu pour les exceptions inattendues.

if et else if sont évalués de haut en bas, et seule la première branche qui correspond s'exécute. Une ValidationError donne aussi true pour instanceof Error : si instanceof Error vient en premier, une ValidationError atterrit elle aussi dans cette branche.

class ValidationError extends Error {
  name = "ValidationError";
}
const error = new ValidationError("Le code postal doit comporter 5 chiffres");

// Si Error est vérifié en premier, une ValidationError atterrit aussi dans cette branche
if (error instanceof Error) {
  console.log("Enregistrement impossible");   // Enregistrement impossible
} else if (error instanceof ValidationError) {
  console.log("Vérifiez votre saisie");       // on n'arrive pas ici
}

// Vérifier d'abord la sous-classe
if (error instanceof ValidationError) {
  console.log("Vérifiez votre saisie");       // Vérifiez votre saisie
} else if (error instanceof Error) {
  console.log("Enregistrement impossible");
}
L'ordre des vérifications décide de la branche
instanceof Errorvérifié d'abordtrue, même pourValidationErrorLes tests suivantsne sont pas faitsAffiche« Enregistrementimpossible »ValidationErrorvérifié d'abordtrue : c'est uneValidationErrorLe test Errorn'est pas faitAffiche « Vérifiezvotre saisie »
Sur la ligne du haut, la première vérification vaut true, donc la seconde n'est jamais testée. Vérifie les types qui héritent d'Error avant Error lui-même.

Les types intégrés comme TypeError héritent eux aussi d'Error : la branche instanceof Error placée en dernier les intercepte donc tous. Sers-toi de cette branche pour afficher un message différent pour les exceptions qu'une correction de la saisie ne réglera pas.

Traite des demandes de changement d'adresse e-mail. ValidationError, changeEmail et requests sont déjà déclarés.

① Passe chaque élément de requests à changeEmail et affiche le résultat.

② Pour une ValidationError, affiche « Vérifiez l'adresse e-mail : » suivi de son message.

③ Pour toute autre Error, affiche « Modification impossible : » suivi du nom du type.

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Joindre l'exception d'origine — cause et exceptions non interceptées

Prenons le cas d'une SyntaxError levée dans une fonction qui lit la réponse d'un service de paiement. Si tu te contentes de la relancer, son type n'indique pas à l'appelant que l'échec s'est produit pendant le traitement d'un paiement. Mais si tu la remplaces par une exception d'un nouveau type, tu perds l'information qu'il s'agissait au départ d'une SyntaxError.

cause (une option qui joint l'exception d'origine à une nouvelle exception) se place dans le deuxième argument, comme dans new Error("message", { cause: error }). Cela fonctionne de la même façon avec les types créés avec extends Error, et le code qui intercepte la nouvelle exception peut lire l'exception d'origine dans error.cause.

class PaymentError extends Error {
  name = "PaymentError";
}

function readPayment(text) {
  try {
    return JSON.parse(text);
  } catch (error) {
    // Joindre l'exception d'origine comme cause et la lever comme erreur de paiement
    throw new PaymentError("Impossible de lire le résultat du paiement", { cause: error });
  }
}

try {
  readPayment('{"orderId":"P-3107","amount":}');
} catch (error) {
  console.log(error.name);          // PaymentError
  console.log(error.message);       // Impossible de lire le résultat du paiement
  console.log(error.cause.name);    // SyntaxError
}
L'exception d'origine reste dans cause
error — la PaymentError levée par readPayment
  • error.name vaut PaymentError
  • error.message vaut Impossible de lire le résultat du paiement
error.cause — l'exception d'origine levée par JSON.parse
  • error.cause.name vaut SyntaxError
  • error.cause.message est la description fournie par JSON.parse, inchangée
L'exception externe est une PaymentError, et cause contient la SyntaxError d'origine. L'exception d'origine est conservée intacte dans error.cause.

Si tu remplaces l'exception sans cause, error.cause vaut undefined, et tu ne peux plus lire ni le type ni la description d'origine. Avec cause, l'appelant peut se servir de error.name pour afficher un message d'échec de paiement, tout en consignant le contenu de error.cause.

Une exception que personne n'intercepte arrête l'exécution

Si aucun catch ne reçoit une exception levée, elle devient une exception non interceptée (une exception qu'aucun catch ne traite), et l'exécution s'arrête sur-le-champ ; aucune des lignes restantes ne s'exécute. Si tu retires le try externe du code ci-dessus, une erreur s'affiche avec le nom du type et le message, comme PaymentError: Impossible de lire le résultat du paiement.

Importe des relevés de pointage. ImportError et records sont déjà déclarés.

① Dans importRecords, prends les cinq premiers caractères de chaque heure d'arrivée et affiche le résultat sous la forme « E-104 : 09:02 ».

② En cas d'échec, lève une ImportError avec le message « Import interrompu à la ligne de E-105 », en y joignant l'exception d'origine.

③ Appelle importRecords et, en cas d'échec, affiche le message, puis « Exception d'origine : » suivi du nom du type de l'exception d'origine.

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat
QUIZ

Vérification des connaissances

Répondez à chaque question une par une.

Question 1Quel est le name d'une exception créée à partir de class ShippingError extends Error {} ?

Question 2Si tu vérifies instanceof Error en premier, dans quelle branche va une exception ValidationError ?

Question 3Tu as intercepté avec catch (error) une exception levée avec cause jointe. Comment lis-tu le nom de l'exception d'origine ?