Quel que soit votre niveau de compétence, vos erreurs ou vos exceptions font partie de la vie du développeur de votre application. L'incohérence du développement Web laisse de nombreux endroits où les erreurs peuvent et se sont produites. La clé de la solution est de traiter toutes les erreurs imprévues (ou prévisibles) pour contrôler l'expérience de l'utilisateur. Avec JavaScript, il existe une variété de fonctionnalités techniques et linguistiques qui peuvent être utilisées pour résoudre correctement n'importe quel problème.
Il est dangereux de gérer les erreurs en JavaScript. Si vous croyez en la loi de Murphy, ce qui va mal se passera mal! Dans cet article, je vais fouiller dans la gestion des erreurs en JavaScript. J'impliquerai quelques pièges et bonnes pratiques. Enfin, nous discuterons du traitement du code asynchrone et de l'AJAX.
Je pense que le modèle JavaScript axé sur les événements ajoute un sens riche à cette langue. Je pense qu'il n'y a pas de différence entre le moteur axé sur l'événement et le mécanisme de rapport d'erreur de ce type de navigateur. Chaque fois qu'une erreur se produit, elle équivaut à lancer un événement à un certain moment. En théorie, nous pouvons gérer des événements de lancement d'erreurs en JavaScript comme nous gérons les événements ordinaires. Si cela vous semble étrange, concentrez-vous sur le démarrage du voyage ci-dessous. Cet article ne cible que le javascript du client.
Exemple
Les exemples de code utilisés dans cet article peuvent être obtenus sur GitHub. La page actuelle ressemble à ceci:
Cliquez sur chaque bouton lancera une erreur. Il simule l'exception du type de perception de type. Ce qui suit est la définition et le test unitaire d'un tel module.
Function Error () {var foo = {}; return foo.bar ();}Tout d'abord, cette fonction définit un objet vide foo. Notez que la méthode BAR () n'est définie nulle part. Nous utilisons des tests unitaires pour vérifier que cela lance une erreur.
it ('lance un typeError', function () {devrait.throws (cible, typeError);});Ce test unitaire utilise des affirmations de test des bibliothèques Mocha et Deft.js. Mocha est un cadre de test en cours d'exécution, et devrait.js est une bibliothèque d'assertion. Si vous ne les connaissez pas très bien, vous pouvez parcourir leurs documents en ligne gratuitement. Un cas de test commence généralement par lui («description») et se termine par le passage ou la défaillance de l'affirmation dans devrait. L'avantage de l'utilisation de ce cadre est qu'il peut être testé unitaire dans le nœud, plutôt que dans le navigateur. Je vous suggère de passer ces tests au sérieux car ils valident de nombreux concepts de base clés en JavaScript.
Comme indiqué ci-dessus, Error () définit un objet vide puis essaie d'y appeler la méthode. Parce que la méthode BAR () n'existe pas dans cet objet, elle lancera une exception. Croyez-moi, dans des langages dynamiques comme JavaScript, n'importe qui peut faire de telles erreurs.
Mauvaise démonstration
Jetons un coup d'œil aux mauvaises méthodes de gestion des erreurs. Je vais résumer la mauvaise action et la lier au bouton. Voici à quoi ressemble le test unitaire du gestionnaire:
fonction badhandler (fn) {try {return fn (); } catch (e) {} return null;}Cette fonction de traitement reçoit une fonction de rappel FN comme dépendance. Ensuite, la fonction est appelée à l'intérieur du gestionnaire. Cette unité de test exemples comment utiliser cette méthode.
Il ('renvoie une valeur sans erreurs', function () {var fn = function () {return 1;}; var result = cible (fn); result. devrait.equal (1);}); it ('renvoie un null avec des erreurs', function () {var fn = function () {throw error ('random error');}; var résultat = cible (fn); devrait (résultat) .equal (null);});Comme vous pouvez le voir, si une erreur se produit, cette étrange façon de gérer renvoie un null. Cette fonction de rappel fn () pointera vers une méthode ou une erreur juridique. L'événement de traitement de clic ci-dessous termine le reste.
(Function (Handler, bomb) {var badbutton = document.getElementById ('bad'); if (badbutton) {badbutton.addeventListener ('click', function () {handler (bomb); console.log ('imaginaire, se faire promotion pour cacher les erreurs');});}} (bad-randler, erre.La mauvaise chose, c'est que ce que je viens de recevoir était nul. Cela m'a rendu très confus quand je pensais déterminer ce qui n'allait pas. Cette stratégie de silence lorsque des erreurs se produisent couvre chaque lien de la conception de l'expérience utilisateur à la corruption des données. Le côté frustrant a suivi était que je devais passer des heures à déboguer, mais je n'ai pas pu voir l'erreur dans le bloc de code d'essai. Ce traitement étrange cache toutes les erreurs dans le code, et il suppose que tout est normal. Cela peut être exécuté en douceur dans certaines équipes qui ne prêtent pas attention à la qualité du code. Cependant, ces erreurs cachées vous forceront éventuellement à passer des heures à déboguer votre code. Dans une solution multicouche qui repose sur la pile d'appels, il est possible de déterminer d'où vient l'erreur. Il peut être approprié de gérer silencieusement le coup d'essai dans de rares cas. Mais si vous rencontrez une erreur, vous vous en occuperez, ce qui n'est pas une bonne solution.
Cet échec, c'est-à-dire que la stratégie de silence vous incitera à mieux gérer les erreurs dans votre code. JavaScript fournit un moyen plus élégant de traiter ce type de problème.
Solution non lisible
Continuons, jetons un coup d'œil à l'approche difficile à comprendre. Je vais sauter la partie étroitement couplée au DOM. Cette partie n'est pas différente de la mauvaise approche que nous venons de voir. L'accent est mis sur la partie du test unitaire ci-dessous qui gère les exceptions.
fonction uglyhandler (fn) {try {return fn (); } catch (e) {TROME ERROR ('Une nouvelle erreur'); }} it ('Renvoie une nouvelle erreur avec Errors', function () {var fn = function () {throw new typeError ('Type Error');}; devrait.throws (function () {cible (fn);}, erreur);});Il y a une bonne amélioration par rapport au mauvais traitement tout à l'heure. L'exception est jetée dans la pile d'appels. Ce que j'aime, c'est que les erreurs sont libérées de la pile, ce qui est extrêmement utile pour le débogage. Lorsqu'une exception est lancée, l'interprète examinera la prochaine fonction de traitement à un niveau de la pile d'appels. Cela offre de nombreuses occasions de gérer les erreurs au niveau supérieur de la pile d'appels. Malheureusement, parce qu'il est une erreur difficile, je ne vois pas les informations d'erreur d'origine. Je dois donc regarder le long de la pile d'appels et trouver l'exception la plus primitive. Mais au moins, je sais qu'il y a une erreur qui se produit où l'exception est lancée.
Bien que cette gestion des erreurs illisible soit inoffensive, elle rend le code difficile à comprendre. Voyons comment le navigateur gère les erreurs.
Pile d'appels
Ensuite, une façon de lancer une exception est d'ajouter un bloc d'essai ... Catch Code au niveau supérieur de la pile d'appels. Par exemple:
fonction main (bomb) {try {bomb (); } catch (e) {// gérer toutes les choses d'erreur}}Mais rappelez-vous que j'ai dit que les navigateurs sont axés sur les événements? Oui, une exception en JavaScript n'est rien de plus qu'un événement. L'interprète arrête le programme au contexte où l'exception se produit actuellement et lance une exception. Pour confirmer cela, ce qui suit est une fonction de gestion des événements mondiaux sur ONERROR que nous pouvons voir. Cela ressemble à ceci:
window.addeventListener ('error', fonction (e) {var error = e.error; console.log (erreur);});Ce gestionnaire d'événements capture des erreurs dans l'environnement d'exécution. Les événements d'erreur produiront diverses erreurs à divers endroits. Le point de cette approche est de gérer de manière centralisée les erreurs dans le code. Tout comme les autres événements, vous pouvez utiliser un gestionnaire mondial pour gérer diverses erreurs. Cela ne fait que la gestion des erreurs qu'un seul objectif si vous adhérez aux principes de substitution solide (responsabilité unique, à l'ouverture ouverte, de substitution de Liskov, de ségrégation d'interface et de dépendance). Vous pouvez enregistrer les fonctions de traitement des erreurs à tout moment. L'interprète exécute ces boucles de fonctions. Le code est libéré des déclarations remplies de Try ... Catch et devient facile à déboguer. La clé de cette approche est de gérer les erreurs qui se produisent comme des événements JavaScript normaux.
Maintenant, il existe un moyen d'afficher la pile d'appels avec une fonction de traitement globale. Que pouvons-nous en faire? Après tout, nous devons utiliser la pile d'appels.
Enregistrez la pile d'appels
La pile d'appels est très utile pour gérer les corrections de bogues. La bonne nouvelle est que le navigateur fournit ces informations. Même si l'attribut de pile de l'objet d'erreur n'est pas standard actuellement, cet attribut est généralement pris en charge dans les navigateurs plus récents.
Ainsi, la chose cool que nous pouvons faire est de l'imprimer au serveur:
window.addeventListener ('error', function (e) {var stack = e.error.stack; var message = e.error.toString (); if (stack) {message + = '/ n' + pile;} var xhr = new xmlhttprequest (); xhr.open ('post', '/ log', true); xhr.Send (message);});Il peut ne pas être évident dans l'exemple de code, mais ce gestionnaire d'événements sera déclenché par le code d'erreur précédent. Comme mentionné ci-dessus, chaque gestionnaire a un seul objectif, ce qui rend le code sec (ne vous répétez pas sans faire la roue à plusieurs reprises). Ce qui m'intéresse, c'est comment capturer ces messages sur le serveur.
Voici une capture d'écran de l'exécution du nœud:
La pile d'appels est très utile pour déboguer le code. Ne sous-estimez jamais le rôle de la pile d'appels.
Traitement asynchrone
Oh, c'est assez dangereux de gérer le code asynchrone! JavaScript apporte un code asynchrone à partir de l'environnement d'exécution actuel. Cela signifie qu'il y a un problème avec la déclaration d'essai ... Catch ci-dessous.
fonction asynchandler (fn) {try {setTimeout (function () {fn ();}, 1); } catch (e) {}}Il reste encore une partie restante de ce test unitaire:
Il ('n'attrape pas des exceptions avec des erreurs', function () {var fn = function () {throw new typeError ('Type Error');}; faillitPromise (function () {Target (fn);}). devrait.be.rejectedwith (typeError);}); funcyPromise (fn) {return new (function (résolve, reject) {reject (fn); });}Je dois mettre fin à ce gestionnaire avec une promesse de vérifier l'exception. Notez que bien que mon code soit tout en essayant ... Catch, une exception non perdue apparaît toujours. Oui, essayez ... Catch fonctionne uniquement dans un environnement d'exécution distinct. Lorsque l'exception est lancée, l'environnement d'exécution de l'interprète n'est plus le bloc de capture d'essai actuel. Ce comportement se produit de manière similaire aux appels AJAX. Donc, maintenant il y a deux options. Une alternative consiste à prendre des exceptions dans un rappel asynchrone:
setTimeout (function () {try {fn ();} catch (e) {// gère cette erreur asynchrone}}, 1);Bien que cette méthode soit utile, il y a encore beaucoup de place à l'amélioration. Tout d'abord, essayez ... les blocs de code de capture apparaissent partout dans le code. En fait, dans les appels de programmation des années 1970, ils voulaient que leur code se repose. De plus, le moteur V8 décourage l'utilisation des blocs de code Try… Catch dans les fonctions (V8 est le moteur JavaScript utilisé par le navigateur et le nœud Chrome). Ils recommandent d'écrire ces blocs de code qui attirent des exceptions en haut de la pile d'appels.
Alors, qu'est-ce que cela nous dit? Comme je l'ai dit ci-dessus, les gestionnaires d'erreurs globaux dans n'importe quel contexte d'exécution sont nécessaires. Si vous ajoutez un gestionnaire d'erreurs à un objet Window, c'est-à-dire que vous avez terminé! N'est-il pas agréable de suivre les principes de sec et de solide? Un gestionnaire d'erreurs global gardera votre code lisible et propre.
Ce qui suit est le rapport imprimé sur la gestion des exceptions côté serveur. Notez que si vous utilisez le code dans l'exemple, la sortie peut varier légèrement en fonction du navigateur que vous utilisez.
Ce gestionnaire peut même me dire quelle erreur vient du code asynchrone. Il me dit que l'erreur provient du gestionnaire setTimeout (). Tellement cool!
Les erreurs font partie de chaque application, mais la gestion des erreurs appropriée ne l'est pas. Il existe au moins deux façons de gérer les erreurs. L'une est une solution d'échec, le silence, c'est-à-dire ignorer les erreurs dans le code. Une autre façon consiste à détecter et à résoudre rapidement les erreurs, c'est-à-dire à arrêter à l'erreur et à se reproduire. Je pense que j'ai exprimé clairement ce que j'aime et pourquoi je l'aime. Mon choix: ne cachez pas le problème. Personne ne vous blâmera pour des événements inattendus dans votre programme. Ceci est acceptable, pour casser les points, reproduire et essayer l'utilisateur. Dans un monde imparfait, il est important de vous donner une chance. Les erreurs sont inévitables et ce que vous faites est important pour résoudre l'erreur. L'utilisation raisonnable des fonctionnalités de gestion des erreurs de JavaScript et du décodage automatique et flexible peuvent rendre l'expérience de l'utilisateur plus fluide, et également faciliter le diagnostic du développeur.