Le guide ultime de la gestion des erreurs JavaScript —

Ce didacticiel plonge dans la gestion des erreurs JavaScript afin que vous puissiez lancer, détecter et gérer vos propres erreurs.
Contenu:
- Afficher un message d’erreur est le dernier recours
- Comment JavaScript traite les erreurs
- Attraper les exceptions
- Types d’erreurs JavaScript standard
- Erreur globale
- Lancer nos propres exceptions
- Erreurs de fonction asynchrone
- Erreurs basées sur les promesses
- Gestion des exceptions exceptionnelles
Les développeurs experts s’attendent à l’inattendu. Si quelque chose peut mal tourner, ça ira mal — généralement, au moment où le premier utilisateur accède à votre nouveau système Web.
Nous pouvons éviter certaines erreurs d’application Web comme suit :
- Un bon éditeur ou linter peut détecter les erreurs de syntaxe.
- Une bonne validation peut détecter les erreurs de saisie de l’utilisateur.
- Des processus de test robustes peuvent détecter des erreurs logiques.
Pourtant des erreurs subsistent. Les navigateurs peuvent échouer ou ne pas prendre en charge une API que nous utilisons. Les serveurs peuvent tomber en panne ou mettre trop de temps à répondre. La connectivité réseau peut échouer ou devenir peu fiable. Les problèmes peuvent être temporaires, mais nous ne pouvons pas contourner de tels problèmes. Cependant, nous pouvons anticiper les problèmes, prendre des mesures correctives et rendre notre application plus résiliente.
Afficher un message d’erreur est le dernier recours
Idéalement, les utilisateurs ne devraient jamais voir de messages d’erreur.
Nous pourrons peut-être ignorer des problèmes mineurs, tels qu’une image décorative qui ne se charge pas. Nous pourrions résoudre des problèmes plus graves tels que les échecs de sauvegarde de données Ajax en stocker les données localement et télécharger plus tard. Une erreur ne devient nécessaire que lorsque l’utilisateur risque de perdre des données — en supposant qu’ils peuvent faire quelque chose à ce sujet.
Il est donc nécessaire de détecter les erreurs au fur et à mesure qu’elles se produisent et de déterminer la meilleure action. La génération et la détection d’erreurs dans une application JavaScript peuvent être intimidantes au début, mais c’est peut-être plus facile que prévu.
Comment JavaScript traite les erreurs
Lorsqu’une instruction JavaScript génère une erreur, on dit qu’elle lancer une exception. JavaScript crée et lance un Error
objet décrivant l’erreur. Nous pouvons voir cela en action dans cette démo CodePen. Si nous fixons le décimales à un nombre négatif, nous verrons un message d’erreur dans la console en bas. (Notez que nous n’intégrons pas les CodePens dans ce didacticiel, car vous devez être en mesure de voir la sortie de la console pour qu’ils aient un sens.)
Le résultat ne sera pas mis à jour, et nous verrons un RangeError
message dans la console. La fonction suivante génère l’erreur lorsque dp
est négatif :
function divide(v1, v2, dp) {
return (v1 / v2).toFixed(dp);
}
Après avoir renvoyé l’erreur, l’interpréteur JavaScript vérifie le code de gestion des exceptions. Aucun n’est présent dans le divide()
fonction, il vérifie donc la fonction appelante :
function showResult() {
result.value = divide(
parseFloat(num1.value),
parseFloat(num2.value),
parseFloat(dp.value)
);
}
L’interpréteur répète le processus pour chaque fonction sur la pile des appels jusqu’à ce que l’une de ces choses se produise :
- il trouve un gestionnaire d’exceptions
- il atteint le niveau supérieur du code (ce qui provoque l’arrêt du programme et affiche une erreur dans la console, comme illustré dans l’exemple CodePen ci-dessus)
Attraper les exceptions
Nous pouvons ajouter un gestionnaire d’exceptions au divide()
fonctionner avec un essayez… attrapez le bloc:
function divide(v1, v2, dp) {
try {
return (v1 / v2).toFixed(dp);
}
catch(e) {
console.log(`
error name : ${ e.name }
error message: ${ e.message }
`);
return 'ERROR';
}
}
Ceci exécute le code dans le try {}
bloquer mais, lorsqu’une exception se produit, le catch {}
block s’exécute et reçoit l’objet d’erreur renvoyé. Comme précédemment, essayez de régler le décimales à un nombre négatif dans cette démo CodePen.
Les résultat montre maintenant ERREUR. La console affiche le nom et le message d’erreur, mais cela est produit par le console.log
instruction et ne termine pas le programme.
Remarque : cette démonstration d’un try...catch
bloc est exagéré pour une fonction de base telle que divide()
. C’est plus simple d’assurer dp
est égal ou supérieur à zéro, comme nous le verrons ci-dessous.
Nous pouvons définir une option finally {}
bloquer si nous avons besoin que le code s’exécute lorsque le try
ou alors catch
code exécute :
function divide(v1, v2, dp) {
try {
return (v1 / v2).toFixed(dp);
}
catch(e) {
return 'ERROR';
}
finally {
console.log('done');
}
}
Les sorties de la console "done"
, si le calcul réussit ou génère une erreur. UN finally
block exécute généralement des actions que nous aurions autrement besoin de répéter à la fois dans le try
et le catch
bloquer – comme l’annulation d’un appel d’API ou la fermeture d’une connexion à la base de données.
UN try
bloc nécessite soit un catch
bloquer, un finally
bloquer, ou les deux. Notez que, lorsqu’un finally
bloc contient un return
instruction, cette valeur devient la valeur de retour pour toute la fonction ; autre return
déclarations dans try
ou alors catch
les blocs sont ignorés.
Gestionnaires d’exceptions imbriqués
Que se passe-t-il si nous ajoutons un gestionnaire d’exceptions à l’appel showResult()
une fonction?
function showResult() {
try {
result.value = divide(
parseFloat(num1.value),
parseFloat(num2.value),
parseFloat(dp.value)
);
}
catch(e) {
result.value = 'FAIL!';
}
}
La réponse est … rien! Cette catch
bloc n’est jamais atteint, car le catch
bloquer dans le divide()
La fonction gère l’erreur.
Cependant, nous pourrions programmer jeter un nouveau Error
objet dans divide()
et éventuellement passer l’erreur d’origine dans un cause
propriété du second argument :
function divide(v1, v2, dp) {
try {
return (v1 / v2).toFixed(dp);
}
catch(e) {
throw new Error('ERROR', { cause: e });
}
}
Cela déclenchera le catch
bloc dans la fonction appelante :
function showResult() {
try {
}
catch(e) {
console.log( e.message );
console.log( e.cause.name );
result.value = 'FAIL!';
}
}
Types d’erreurs JavaScript standard
Lorsqu’une exception se produit, JavaScript crée et lève un objet décrivant l’erreur en utilisant l’un des types suivants.
Erreur de syntaxe
Une erreur renvoyée par un code dont la syntaxe n’est pas valide, comme une parenthèse manquante :
if condition) {
console.log('condition is true');
}
Remarque : les langages tels que C++ et Java signalent des erreurs de syntaxe lors de la compilation. JavaScript est un langage interprété, les erreurs de syntaxe ne sont donc pas identifiées tant que le code n’est pas exécuté. Tout bon éditeur de code ou linter peut repérer les erreurs de syntaxe avant d’essayer d’exécuter du code.
Erreur de référence
Une erreur levée lors de l’accès à une variable inexistante :
function inc() {
value++;
}
Encore une fois, les bons éditeurs de code et les linters peuvent détecter ces problèmes.
Erreur-type
Une erreur générée lorsqu’une valeur n’est pas d’un type attendu, comme l’appel d’une méthode d’objet inexistante :
const obj = {};
obj.missingMethod();
RangeError
Une erreur générée lorsqu’une valeur ne se trouve pas dans l’ensemble ou la plage de valeurs autorisées. Les méthode toFixed() utilisé ci-dessus génère cette erreur, car il attend une valeur généralement comprise entre 0 et 100 :
const n = 123.456;
console.log( n.toFixed(-1) );
URIError
Une erreur générée par les fonctions de gestion d’URI telles que encodeURI() et decodeURI() lorsqu’ils rencontrent des URI malformés :
const u = decodeURIComponent('%');
EvalErreur
Une erreur est générée lors du passage d’une chaîne contenant du code JavaScript invalide au fonction eval():
eval('console.logg x;');
Remarque : veuillez ne pas utiliser eval()
! L’exécution de code arbitraire contenu dans une chaîne éventuellement construite à partir d’une entrée utilisateur est bien trop dangereuse !
Erreur globale
Une erreur levée lorsque plusieurs erreurs sont enveloppées dans une seule erreur. Ceci est généralement déclenché lors de l’appel d’une opération telle que Promesse.tout()qui renvoie les résultats de n’importe quel nombre de promesses.
Erreur interne
Une erreur non standard (Firefox uniquement) générée lorsqu’une erreur se produit en interne dans le moteur JavaScript. C’est généralement le résultat de quelque chose qui prend trop de mémoire, comme un grand tableau ou « trop de récursivité ».
Erreur
Enfin, il existe un générique Error
objet qui est le plus souvent utilisé lors de l’implémentation de nos propres exceptions … que nous aborderons ensuite.
Lancer nos propres exceptions
Nous pouvons throw
nos propres exceptions lorsqu’une erreur se produit — ou devrait se produire. Par example:
- notre fonction ne reçoit pas de paramètres valides
- une requête Ajax ne parvient pas à renvoyer les données attendues
- une mise à jour DOM échoue car un nœud n’existe pas
Les throw
accepte en fait n’importe quelle valeur ou objet. Par example:
throw 'A simple error string';
throw 42;
throw true;
throw { message: 'An error', name: 'MyError' };
Des exceptions sont lancées à chaque fonction de la pile des appels jusqu’à ce qu’elles soient interceptées par une exception (catch
) gestionnaire. Plus concrètement, cependant, nous voudrons créer et jeter un Error
chose elles agissent donc de la même manière que les erreurs standard générées par JavaScript.
Nous pouvons créer un générique Error
objet en passant un message optionnel au constructeur :
throw new Error('An error has occurred');
Nous pouvons également utiliser Error
comme une fonction sans new
. Il renvoie une Error
objet identique à celui ci-dessus :
throw Error('An error has occurred');
Nous pouvons éventuellement passer un nom de fichier et un numéro de ligne comme deuxième et troisième paramètres :
throw new Error('An error has occurred', 'script.js', 99);
Ceci est rarement nécessaire, car ils utilisent par défaut le fichier et la ligne où nous avons jeté le Error
chose. (Ils sont également difficiles à maintenir car nos fichiers changent !)
On peut définir le générique Error
objets, mais nous devrions utiliser un Type d’erreur standard quand c’est possible. Par example:
throw new RangeError('Decimal places must be 0 or greater');
Tous Error
les objets ont les propriétés suivantes, que nous pouvons examiner dans un catch
bloc:
.name
: le nom du type d’erreur — tel queError
ou alorsRangeError
.message
: le message d’erreur
Les propriétés non standard suivantes sont également prises en charge dans Firefox :
.fileName
: le fichier où l’erreur s’est produite.lineNumber
: le numéro de la ligne où l’erreur s’est produite.columnNumber
: le numéro de colonne sur la ligne où l’erreur s’est produite.stack
: une trace de pile répertoriant les appels de fonction effectués avant que l’erreur ne se produise
Nous pouvons changer le divide()
fonction pour lancer un RangeError
lorsque le nombre de décimales n’est pas un nombre, est inférieur à zéro ou supérieur à huit :
function divide(v1, v2, dp) {
if (isNaN(dp) || dp < 0 || dp > 8) {
throw new RangeError('Decimal places must be between 0 and 8');
}
return (v1 / v2).toFixed(dp);
}
De même, nous pourrions jeter un Error
ou alors TypeError
quand le dividende la valeur n’est pas un nombre à empêcher NaN
résultats:
if (isNaN(v1)) {
throw new TypeError('Dividend must be a number');
}
Nous pouvons également prendre en charge diviseurs non numériques ou nuls. JavaScript renvoie Infini lors de la division par zéro, mais cela pourrait dérouter les utilisateurs. Plutôt que d’élever un générique Error
nous pourrions créer une coutume DivByZeroError
type d’erreur :
class DivByZeroError extends Error {
constructor(message) {
super(message);
this.name = 'DivByZeroError';
}
}
Lancez-le ensuite de la même manière :
if (isNaN(v2) || !v2) {
throw new DivByZeroError('Divisor must be a non-zero number');
}
Ajoutez maintenant un try...catch
bloquer l’appel showResult()
une fonction. Il peut recevoir n’importe Error
tapez et réagissez en conséquence – dans ce cas, en affichant le message d’erreur :
function showResult() {
try {
result.value = divide(
parseFloat(num1.value),
parseFloat(num2.value),
parseFloat(dp.value)
);
errmsg.textContent = '';
}
catch (e) {
result.value = 'ERROR';
errmsg.textContent = e.message;
console.log( e.name );
}
}
Essayez d’entrer des valeurs non numériques, nulles et négatives non valides dans cette démo CodePen.
La version finale du divide()
la fonction vérifie toutes les valeurs d’entrée et lance un Error
quand c’est nécessaire:
function divide(v1, v2, dp) {
if (isNaN(v1)) {
throw new TypeError('Dividend must be a number');
}
if (isNaN(v2) || !v2) {
throw new DivByZeroError('Divisor must be a non-zero number');
}
if (isNaN(dp) || dp < 0 || dp > 8) {
throw new RangeError('Decimal places must be between 0 and 8');
}
return (v1 / v2).toFixed(dp);
}
Il n’est plus nécessaire de placer un try...catch
bloquer autour de la finale return
, puisqu’il ne devrait jamais générer d’erreur. Si cela se produisait, JavaScript générerait sa propre erreur et la traiterait par le catch
bloquer dans showResult()
.
Erreurs de fonction asynchrone
Nous ne pouvons pas intercepter les exceptions levées par les fonctions asynchrones basées sur le rappel, car une erreur est levée après le try...catch
bloc termine l’exécution. Ce code semble correct, mais le catch
bloc ne s’exécutera jamais et la console affiche un Uncaught Error
message après une seconde :
function asyncError(delay = 1000) {
setTimeout(() => {
throw new Error('I am never caught!');
}, delay);
}
try {
asyncError();
}
catch(e) {
console.error('This will never run');
}
La convention présumée dans la plupart des frameworks et des runtimes de serveur tels que Node.js est de renvoyer une erreur comme premier paramètre d’une fonction de rappel. Cela ne déclenchera pas d’exception, bien que nous puissions lancer manuellement une Error
si nécessaire:
function asyncError(delay = 1000, callback) {
setTimeout(() => {
callback('This is an error message');
}, delay);
}
asyncError(1000, e => {
if (e) {
throw new Error(`error: ${ e }`);
}
});
Erreurs basées sur les promesses
Les rappels peuvent devenir compliqués, il est donc préférable d’utiliser promesses lors de l’écriture de code asynchrone. Lorsqu’une erreur se produit, la promesse est reject()
méthode peut retourner un nouveau Error
objet ou toute autre valeur :
function wait(delay = 1000) {
return new Promise((resolve, reject) => {
if (isNaN(delay) || delay < 0) {
reject( new TypeError('Invalid delay') );
}
else {
setTimeout(() => {
resolve(`waited ${ delay } ms`);
}, delay);
}
})
}
Remarque : les fonctions doivent être soit 100 % synchrones, soit 100 % asynchrones. C’est pourquoi il faut vérifier le delay
valeur à l’intérieur de la promesse retournée. Si nous avons vérifié le delay
valeur et a renvoyé une erreur avant renvoyant la promesse, la fonction deviendrait synchrone lorsqu’une erreur se produirait.
Les Méthode Promise.catch() s’exécute lors du passage d’un invalide delay
paramètre et il reçoit le retour Error
chose:
wait('INVALID')
.then( res => console.log( res ))
.catch( e => console.error( e.message ) )
.finally( () => console.log('complete') );
Personnellement, je trouve les chaînes de promesses un peu difficiles à lire. Heureusement, nous pouvons utiliser await
pour appeler n’importe quelle fonction qui renvoie une promesse. Cela doit se produire à l’intérieur d’un async
fonction, mais nous pouvons capturer les erreurs en utilisant une norme try...catch
bloc.
Les suivants (immédiatement invoqués) async
fonction est fonctionnellement identique à la chaîne de promesses ci-dessus :
(async () => {
try {
console.log( await wait('INVALID') );
}
catch (e) {
console.error( e.message );
}
finally {
console.log('complete');
}
})();
Gestion des exceptions exceptionnelles
Lancement Error
objets et la gestion des exceptions est facile en JavaScript :
try {
throw new Error('I am an error!');
}
catch (e) {
console.log(`error ${ e.message }`)
}
Construire une application résiliente qui réagit de manière appropriée aux erreurs et facilite la vie des utilisateurs est plus difficile. Attendez-vous toujours à l’inattendu.
Informations complémentaires :
Source link