Fermer

janvier 31, 2023

Le guide ultime de la gestion des erreurs JavaScript —

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:

  1. Afficher un message d’erreur est le dernier recours
  2. Comment JavaScript traite les erreurs
  3. Attraper les exceptions
  4. Types d’erreurs JavaScript standard
  5. Erreur globale
  6. Lancer nos propres exceptions
  7. Erreurs de fonction asynchrone
  8. Erreurs basées sur les promesses
  9. 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 que Error ou alors RangeError
  • .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 Errornous 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