Peu importe notre niveau en programmation, nos scripts comportent parfois des erreurs. Elles peuvent être dues à nos erreurs, à une entrée utilisateur imprévue, à une réponse erronée du serveur et à mille autres raisons.
Généralement, un script âmeurtâ (sâarrête immédiatement) en cas dâerreur, en lâaffichant dans la console.
Mais il existe une structure de syntaxe try...catch qui permet au script âdâattraperâ les erreurs et, au lieu de mourir en cas de problème, de faire quelque chose de plus raisonnable.
La syntaxe âtryâ¦catchâ
La structure try...catch a deux blocs principaux : try, puis catch :
try {
// code...
} catch (err) {
// Gestion des erreurs
}
Cela fonctionne comme ceci :
- Tout dâabord, le code dans
try {...}est exécuté. - Sâil nây a pas eu dâerreur, alors
catch(err)est ignoré : lâexécution arrive à la fin detryet continue en ignorantcatch. - Si une erreur survient, alors lâexécution de
tryest arrêtée et le contrôle se place au début decatch(err). La variableerr(qui peut utiliser nâimporte quel nom) contient un objet dâerreur avec des détails sur ce qui sâest passé.
Donc, une erreur dans le bloc try {...} ne tue pas le script â nous avons une opportunité de la gérer dans catch.
Voyons des exemples.
-
Un exemple sans erreur : affiche
alert(1)et(2):try { alert('Start of try runs'); // (1) <-- // ...pas d'erreur alert('End of try runs'); // (2) <-- } catch (err) { alert('Catch is ignored, because there are no errors'); // (3) } -
Un exemple avec une erreur : montre
(1)et(3):try { alert('Start of try runs'); // (1) <-- lalala; // error, variable is not defined! alert('End of try (never reached)'); // (2) } catch (err) { alert(`Error has occurred!`); // (3) <-- }
try...catch ne fonctionne que pour les erreurs dâexécutionPour que try...catch fonctionne, le code doit être exécutable. En dâautres termes, le code doit être du JavaScript valide.
Cela ne fonctionnera pas si le code est syntaxiquement incorrect, par exemple, il a des accolades inégalées :
try {
{{{{{{{{{{{{
} catch (err) {
alert("The engine can't understand this code, it's invalid");
}
Le moteur JavaScript lit dâabord le code, puis lâexécute. Les erreurs qui se produisent lors de la phase de lecture sont appelées erreurs âdâanalyseâ et sont irrécupérables (de lâintérieur de ce code). Câest parce que le moteur ne peut pas comprendre le code.
Ainsi, try...catch ne peut gérer que les erreurs qui se produisent dans du code valide. De telles erreurs sont appelées âerreurs dâexécutionâ ou, parfois, âexceptionsâ.
try...catch fonctionne de manière synchroneSi une exception se produit dans le code âplanifiéâ, comme dans setTimeout, try...catch ne lâattrapera pas :
try {
setTimeout(function() {
noSuchVariable; // le script mourra ici
}, 1000);
} catch (err) {
alert( "won't work" );
}
Câest parce que la fonction elle-même est exécutée ultérieurement, lorsque le moteur a déjà quitté la structure try...catch.
Pour capturer une exception dans une fonction planifiée, try...catch doit être à lâintérieur de cette fonction :
setTimeout(function() {
try {
noSuchVariable; // try...catch gère l'erreur !
} catch {
alert( "error is caught here!" );
}
}, 1000);
Objet dâerreur
En cas dâerreur, JavaScript génère un objet contenant les détails à son sujet. Lâobjet est ensuite passé en argument à catch :
try {
// ...
} catch(err) { // <-- "l'objet d'erreur", pourrait utiliser un autre mot au lieu de err
// ...
}
Pour toutes les erreurs intégrées, lâobjet dâerreur a deux propriétés principales :
name- Nom de lâerreur. Par exemple, pour une variable non définie, il sâagit de
"ReferenceError". message- Message textuel sur les détails de lâerreur.
Il existe dâautres propriétés non standard disponibles dans la plupart des environnements. Lâun des plus largement utilisés et supportés est :
stack- Pile dâexécution en cours : chaîne contenant des informations sur la séquence dâappels imbriqués ayant entraîné lâerreur. Utilisé à des fins de débogage.
Par exemple :
try {
lalala; // error, variable is not defined!
} catch (err) {
alert(err.name); // ReferenceError
alert(err.message); // lalala is not defined
alert(err.stack); // ReferenceError: lalala is not defined at (...call stack)
// Peut aussi montrer une erreur dans son ensemble
// L'erreur est convertie en chaîne comme "name: message"
alert(err); // ReferenceError: lalala is not defined
}
Omission facultative pour âcatchâ
Si nous nâavons pas besoin de détails dâerreur, catch peut lâomettre :
try {
// ...
} catch { // <-- sans (err)
// ...
}
Lâutilisation de âtryâ¦catchâ
Explorons un cas dâutilisation réel de try...catch.
Comme nous le savons déjà , JavaScript prend en charge la méthode JSON.parse(str) pour lire les valeurs encodées en JSON.
Il est généralement utilisé pour décoder les données reçues sur le réseau, par le serveur ou par une autre source.
Nous le recevons et appelons JSON.parse comme ceci :
let json = '{"name":"John", "age": 30}'; // données du serveur
let user = JSON.parse(json); // convertir la représentation textuelle en objet JS
// maintenant, user est un objet avec les propriétés de la chaîne
alert( user.name ); // John
alert( user.age ); // 30
Vous trouverez des informations plus détaillées sur JSON dans le chapitre JSON methods, toJSON.
Si json est malformé, JSON.parse génère une erreur, de sorte que le script âmeurtâ.
Devrions-nous en être satisfaits ? Bien sûr que non !
De cette façon, si quelque chose ne va pas avec les données, le visiteur ne le saura jamais (à moins dâouvrir la console du développeur). Et les gens nâaiment vraiment pas quand quelque chose âmeurtâ sans aucun message dâerreur.
Utilisons try...catch pour gérer lâerreur :
let json = "{ bad json }";
try {
let user = JSON.parse(json); // <-- quand une erreur se produit...
alert( user.name ); // ne fonctionne pas
} catch (err) {
// ...l'exécution saute ici
alert( "Our apologies, the data has errors, we'll try to request it one more time." );
alert( err.name );
alert( err.message );
}
Ici, nous utilisons le bloc catch uniquement pour afficher le message, mais nous pouvons faire beaucoup plus : envoyer une nouvelle requête réseau, suggérer une alternative au visiteur, envoyer des informations sur lâerreur à un système de journalisation, ⦠Bien mieux que de juste mourir.
Lever nos propres exceptions
Que se passe-t-il si json est correct du point de vue syntaxique, mais quâil nâa pas de propriété requise name ?
Comme ceci :
let json = '{ "age": 30 }'; // données incomplètes
try {
let user = JSON.parse(json); // <-- pas d'erreurs
alert( user.name ); // pas de "name" !
} catch (err) {
alert( "doesn't execute" );
}
Ici, JSON.parse fonctionne normalement, mais lâabsence de name est en réalité une erreur pour nous.
Pour unifier le traitement des erreurs, nous allons utiliser lâopérateur throw.
Lâinstruction âthrowâ
Lâinstruction throw génère une erreur.
La syntaxe est la suivante :
throw <error object>
Techniquement, on peut utiliser nâimporte quoi comme objet dâerreur. Cela peut même être une primitive, comme un nombre ou une chaîne, mais il est préférable dâutiliser des objets, de préférence avec les propriétés name et message (pour rester quelque peu compatibles avec les erreurs intégrées).
JavaScript comporte de nombreux constructeurs intégrés pour les erreurs standards : Error, SyntaxError, ReferenceError, TypeError et autres. Nous pouvons également les utiliser pour créer des objets dâerreur.
Leur syntaxe est la suivante :
let error = new Error(message);
// ou
let error = new SyntaxError(message);
let error = new ReferenceError(message);
// ...
Pour les erreurs intégrées (pas pour les objets, mais pour les erreurs), la propriété name est exactement le nom du constructeur. Et message est tiré de lâargument.
Par exemple :
let error = new Error("Things happen o_O");
alert(error.name); // Error
alert(error.message); // Things happen o_O
Voyons quel type dâerreur JSON.parse génère :
try {
JSON.parse("{ bad json o_O }");
} catch (err) {
alert(err.name); // SyntaxError
alert(err.message); // Unexpected token b in JSON at position 2
}
Comme on peut le constater, câest une SyntaxError.
Et dans notre cas, lâabsence de name est une erreur, car les utilisateurs doivent avoir un name.
Alors utilisons throw :
let json = '{ "age": 30 }'; // données incomplètes
try {
let user = JSON.parse(json); // <-- pas d'erreurs
if (!user.name) {
throw new SyntaxError("Incomplete data: no name"); // (*)
}
alert( user.name );
} catch (err) {
alert( "JSON Error: " + err.message ); // JSON Error: Incomplete data: no name
}
à la ligne (*), lâinstruction throw génère une SyntaxError avec le message donné, de la même manière que JavaScript le générerait lui-même. Lâexécution de try sâarrête immédiatement et le flux de contrôle saute dans catch.
Maintenant, catch est devenu un emplacement unique pour toutes les erreurs de traitement : Ã la fois pour JSON.parse et dâautres cas.
Propager une exception
Dans lâexemple ci-dessus, nous utilisons try...catch pour gérer des données incorrectes. Mais est-il possible quâune autre erreur inattendue se produise dans le bloc try {...} ? Comme une erreur de programmation (variable is not defined) ou quelque chose dâautre, pas seulement cette âdonnée incorrecteâ.
Par exemple :
let json = '{ "age": 30 }'; // données incomplètes
try {
user = JSON.parse(json); // <-- oublié de mettre "let" avant user
// ...
} catch (err) {
alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined
// (aucune erreur JSON)
}
Bien sûr, tout est possible ! Les programmeurs font des erreurs. Même dans les utilitaires à code source ouvert utilisés par des millions de personnes pendant des décennies, on découvre soudainement un bug qui conduit à de terribles piratages.
Dans notre cas, try...catch est destiné à intercepter les erreurs âincorrect dataâ. Mais par sa nature, catch obtient toutes les erreurs de try. Ici, une erreur inattendue se produit, mais le même message "JSON Error" est toujours affiché. Câest faux et rend également le code plus difficile à déboguer.
Pour éviter de tels problèmes, nous pouvons utiliser la technique du ârethrowingâ (re-lancement). La règle est simple :
Catch ne doit traiter que les erreurs quâil connaît et ârenvoyerâ toutes les autres.
La technique ârethrowingâ peut être expliqué plus en détail comme :
- Catch obtient toutes les erreurs.
- Dans le bloc
catch (err) {...}nous analysons lâobjet dâerreurerr. - Si nous ne savons pas comment le gérer, nous faisons
throw err.
Habituellement, nous pouvons vérifier le type dâerreur en utilisant lâopérateur instanceof :
try {
user = { /*...*/ };
} catch (err) {
if (err instanceof ReferenceError) {
alert('ReferenceError'); // "ReferenceError" pour avoir accédé à une variable non définie
}
}
Nous pouvons également obtenir le nom de la classe dâerreur à partir de la propriété err.name. Toutes les erreurs natives lâont. Une autre option est de lire err.constructor.name.
Dans le code ci-dessous, nous utilisons la technique de âpropagationâ pour que catch ne traite que SyntaxError :
let json = '{ "age": 30 }'; // données incomplètes
try {
let user = JSON.parse(json);
if (!user.name) {
throw new SyntaxError("Incomplete data: no name");
}
blabla(); // erreur inattendue
alert( user.name );
} catch (err) {
if (err instanceof SyntaxError) {
alert( "JSON Error: " + err.message );
} else {
throw err; // propager (*)
}
}
Lâerreur de lâinstruction throw à la ligne (*) de lâintérieur du bloc catch âsortâ de try...catch et peut être soit capturée par une structure try...catch externe (si elle existe), soit elle arrête le script.
Ainsi, le bloc catch ne traite que les erreurs quâil sait gérer et âignoreâ toutes les autres.
Lâexemple ci-dessous montre comment de telles erreurs peuvent être capturées par un niveau supplémentaire de try...catch :
function readData() {
let json = '{ "age": 30 }';
try {
// ...
blabla(); // error!
} catch (err) {
// ...
if (!(err instanceof SyntaxError)) {
throw err; // propager l'erreur (ne sachant pas comment la gérer)
}
}
}
try {
readData();
} catch (err) {
alert( "External catch got: " + err ); // attrapé !
}
Ici, readData ne sait que gérer SyntaxError, alors que le try...catch extérieur sait comment tout gérer.
tryâ¦catchâ¦finally
Mais ce nâest pas tout.
La structure try...catch peut avoir un bloc de code supplémentaire : finally.
Sâil existe, il sâexécute dans tous les cas :
- après
try, sâil nây a pas eu dâerreur, - après
catch, sâil y a eu des erreurs.
La syntaxe étendue ressemble à ceci :
try {
... try to execute the code ...
} catch (err) {
... handle errors ...
} finally {
... execute always ...
}
Essayez dâexécuter ce code :
try {
alert( 'try' );
if (confirm('Make an error?')) BAD_CODE();
} catch (err) {
alert( 'catch' );
} finally {
alert( 'finally' );
}
Ce code a deux manières de sâexécuter :
- Si vous répondez âYesâ à âMake an error?â, Alors
try -> catch -> finally. - Si vous dites âNoâ, alors
try -> finally.
La clause finally est souvent utilisée lorsque nous commençons à faire quelque chose et que nous voulons le finaliser dans tous les cas de figure.
Par exemple, nous voulons mesurer le temps que prend une fonction de nombre de Fibonacci fib(n). Naturellement, nous pouvons commencer à mesurer avant lâexécution et finir ensuite. Mais que se passe-t-il sâil y a une erreur lors de lâappel de la fonction ? En particulier, la mise en oeuvre de fib(n) dans le code ci-dessous renvoie une erreur pour les nombres négatifs ou non entiers.
La clause finally est un bon endroit pour finir les mesures, quoi quâil arrive.
Ici, finally garantit que le temps sera correctement mesuré dans les deux situations â en cas dâexécution réussie de fib et en cas dâerreur :
let num = +prompt("Enter a positive integer number.", 35)
let diff, result;
function fib(n) {
if (n < 0 || Math.trunc(n) != n) {
throw new Error("Must not be negative, and also an integer.");
}
return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}
let start = Date.now();
try {
result = fib(num);
} catch (err) {
result = 0;
} finally {
diff = Date.now() - start;
}
alert(result || "error occurred");
alert( `execution took ${diff}ms` );
Vous pouvez vérifier en exécutant le code en entrant 35 dans prompt â il sâexécute normalement, finally après try. Puis entrez -1 â il y aura une erreur immédiate, puis lâexécution prendra 0ms. Les deux mesures sont effectuées correctement.
En dâautres termes, la fonction peut finir par return ou throw, cela nâa pas dâimportance. La clause finally sâexécute dans les deux cas.
try...catch...finallyVeuillez noter que les variables result et diff dans le code ci-dessus sont déclarées avant try...catch.
Sinon, si nous déclarions let dans le bloc try, il ne serait visible quâà lâintérieur de celui-ci.
finally et returnLa clause finally fonctionne pour toute sortie de try...catch. Cela inclut un return explicite.
Dans lâexemple ci-dessous, il y a return dans try. Dans ce cas, finally est exécuté juste avant que le contrôle ne retourne au code externe.
function func() {
try {
return 1;
} catch (err) {
/* ... */
} finally {
alert( 'finally' );
}
}
alert( func() ); // en premier l'alert du `finally`, puis celle-ci (`1`)
try...finallyLa structure try...finally, sans la clause catch, est également utile. Nous lâappliquons lorsque nous ne voulons pas gérer les erreurs ici (les laisser passer), mais nous voulons être sûrs que les processus que nous avons démarrés sont finalisés.
function func() {
// commencer à faire quelque chose qui doit être complété (comme des mesures)
try {
// ...
} finally {
// compléter cette chose, même si tout meurt
}
}
Dans le code ci-dessus, une erreur à lâintérieur de try ressort toujours, car il nây a pas de catch. Mais finally fonctionne avant que le flux dâexécution ne quitte la fonction.
Catch global
Les informations de cette section ne font pas partie du JavaScript de base.
Imaginons que nous ayons une erreur fatale en dehors de try...catch et que le script soit mort. Comme une erreur de programmation ou une autre chose terrible.
Y a-t-il un moyen de réagir à de tels événements ? Nous pouvons vouloir enregistrer lâerreur, montrer quelque chose à lâutilisateur (normalement, ils ne voient pas les messages dâerreur), etc.
Il nây en a pas dans la spécification, mais les environnements le fournissent généralement, car câest vraiment utile. Par exemple, Node.js a process.on("uncaughtException") pour ça. Et dans le navigateur, nous pouvons attribuer une fonction à la propriété window.onerror, qui fonctionnera en cas dâerreur non interceptée.
La syntaxe :
window.onerror = function(message, url, line, col, error) {
// ...
};
message- Message dâerreur.
url- URL du script où lâerreur sâest produite.
line,col- Numéros de ligne et de colonne où une erreur sâest produite.
error- Objet dâerreur.
Par exemple :
<script>
window.onerror = function(message, url, line, col, error) {
alert(`${message}\n At ${line}:${col} of ${url}`);
};
function readData() {
badFunc(); // Whoops, quelque chose s'est mal passé !
}
readData();
</script>
Le rôle du gestionnaire global window.onerror est généralement de ne pas récupérer lâexécution du script â câest probablement impossible en cas dâerreur de programmation, mais dâenvoyer le message dâerreur aux développeurs.
Il existe également des services Web fournissant un journal des erreurs pour de tels cas, comme https://errorception.com ou https://www.muscula.com.
Ils fonctionnent comme ceci :
- Nous nous inscrivons au service et obtenons un morceau de JS (ou une URL de script) à insérer sur des pages.
- Ce script JS définit une fonction
window.onerrorpersonnalisée. - Lorsquâune erreur se produit, une demande réseau à ce sujet est envoyée au service.
- Nous pouvons nous connecter à lâinterface Web du service et voir les erreurs.
Résumé
La structure try...catch permet de gérer les erreurs dâexécution. Cela permet littéralement âdâessayerâ (try) dâexécuter le code et âdâattraperâ (catch) les erreurs qui peuvent sây produire.
La syntaxe est la suivante :
try {
// exécuter ce code
} catch(err) {
// si une erreur s'est produite, alors saute ici
// err est l'objet d'erreur
} finally {
// faire dans tous les cas après try / catch
}
Il peut ne pas y avoir de section catch ou de finally, donc les structures plus courtes try...catch et try...finally sont également valides.
Les objets dâerreur ont les propriétés suivantes :
messageâ le message dâerreur.nameâ la chaîne avec le nom dâerreur (nom du constructeur de lâerreur).stack(non standard, mais bien supporté) â la pile dâexécution au moment de la création de lâerreur.
Si un objet dâerreur nâest pas nécessaire, nous pouvons lâomettre en utilisant catch {...} au lieu de catch(err) {...}.
Nous pouvons également générer nos propres erreurs en utilisant lâopérateur throw. Techniquement, lâargument de throw peut être nâimporte quoi, mais il sâagit généralement dâun objet dâerreur héritant de la classe Error intégrée. Plus dâinformations sur lâextension des erreurs dans le chapitre suivant.
La technique de propagation est un modèle très important de gestion des erreurs : un bloc catch sâattend généralement à un type dâerreur particulier et sait comment le gérer, il doit donc âpropagerâ (renvoyer) les erreurs quâil ne connaît pas.
Même si nous nâavons pas try...catch, la plupart des environnements permettent de configurer un gestionnaire dâerreurs âglobalesâ pour intercepter les erreurs qui âtombentâ. Dans le navigateur, câest window.onerror.
Commentaires
<code>, pour plusieurs lignes â enveloppez-les avec la balise<pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepenâ¦)