Les ingénieurs front-end savent tous que JavaScript possède des capacités de base de gestion des exceptions. Nous pouvons lancer une nouvelle erreur(), et le navigateur lancera également une exception lorsque nous commettons une erreur lors de l'appel de l'API. Mais on estime que la plupart des ingénieurs front-end n'ont jamais envisagé de collecter ces informations anormales
Quoi qu'il en soit, tant que l'erreur JavaScript ne réapparaît pas après l'actualisation, l'utilisateur peut résoudre le problème en actualisant et le navigateur ne plantera pas. Faites simplement comme si cela ne s'était pas produit. Cette hypothèse était vraie avant que les applications à page unique ne deviennent populaires. L'état de l'application Single Page actuelle devient extrêmement compliqué après une période d'exécution. L'utilisateur peut avoir effectué plusieurs opérations de saisie avant d'arriver ici. Doit-elle être actualisée alors qu'elle est censée être actualisée ? Ne faudrait-il pas entièrement refaire l’opération précédente ? Nous devons donc toujours capturer et analyser ces informations d'exception, puis nous pouvons modifier le code pour éviter d'affecter l'expérience utilisateur.
Comment détecter les exceptions
Nous avons écrit notre propre throw new Error(). Bien sûr, nous pouvons l'attraper si nous le voulons, car nous savons exactement où throw est écrit. Cependant, les exceptions qui se produisent lors de l'appel des API du navigateur ne sont pas nécessairement faciles à détecter. Certaines API sont écrites dans les normes pour lever des exceptions, et certaines API ne sont lancées que par des navigateurs individuels en raison de différences ou de défauts d'implémentation. Pour le premier, nous pouvons également l'attraper via try-catch. Pour le second, nous devons écouter l'exception globale puis l'attraper.
essayer-attraper
Si certaines API de navigateur sont connues pour générer des exceptions, nous devons alors placer l'appel en try-catch pour empêcher l'ensemble du programme d'entrer dans un état illégal en raison d'erreurs. Par exemple, window.localStorage est une telle API. Elle lèvera une exception après que l'écriture des données dépasse la limite de capacité. Ce sera également le cas dans le mode de navigation privée de Safari.
Pour les zones qui ne sont pas couvertes par try-catch, si une exception se produit, elle ne peut être interceptée que via window.onerror.
Attributs perdus
Supposons que nous ayons une fonction reportError qui collecte les exceptions capturées, puis les envoie par lots au stockage côté serveur pour interrogation et analyse. Quelles informations souhaitons-nous collecter ? Les informations les plus utiles incluent : le type d'erreur (nom), le message d'erreur (message), l'adresse du fichier de script (script), le numéro de ligne (ligne), le numéro de colonne (colonne) et la trace de la pile (pile). Si une exception est interceptée via try-catch, ces informations se trouvent sur l'objet Error (pris en charge par les navigateurs grand public), donc reportError peut également collecter ces informations. Mais si elle est capturée via window.onerror, nous savons tous que cette fonction événementielle n'a que 3 paramètres, donc les informations inattendues de ces 3 paramètres sont perdues.
Message sérialisé
Si l'objet Error est créé par nous-mêmes, alors error.message est contrôlé par nous. Fondamentalement, quoi que nous mettions dans error.message, le premier paramètre (message) de window.onerror le sera. (Le navigateur apportera en fait de légères modifications, comme l'ajout du préfixe « Erreur non détectée : ».) Par conséquent, nous pouvons sérialiser les propriétés qui nous intéressent (telles que JSON.Stringify) et les stocker dans error.message, puis les lire. dans window.onerror Retirez-le et désérialisez-le. Bien entendu, cela se limite aux objets Error que nous créons nous-mêmes.
Le cinquième paramètre
Les fabricants de navigateurs connaissent également les limitations auxquelles les utilisateurs sont confrontés lorsqu'ils utilisent window.onerror, ils ont donc commencé à ajouter de nouveaux paramètres à window.onerror. Considérant que seul le numéro de ligne mais aucun numéro de colonne ne semble pas très symétrique, IE ajoute d'abord le numéro de colonne et le met dans le quatrième paramètre. Cependant, tout le monde se soucie davantage de savoir s'ils peuvent obtenir la pile complète, donc Firefox a déclaré qu'il serait préférable de mettre la pile dans le cinquième paramètre. Mais Chrome a déclaré qu'il est préférable de placer l'intégralité de l'objet Error dans le cinquième paramètre. Vous pouvez lire toutes les propriétés de votre choix, y compris les propriétés personnalisées. En conséquence, Chrome a évolué plus rapidement et a implémenté une nouvelle signature window.onerror dans Chrome 30, ce qui a conduit à la rédaction du projet standard en conséquence.
Les noms des attributs de l'objet Error dont nous avons parlé précédemment sont basés sur la méthode de dénomination de Chrome. Cependant, différents navigateurs nomment les attributs de l'objet Error différemment. Par exemple, l'adresse du fichier de script est appelée script dans Chrome mais est appelée nom de fichier dans Firefox. . Par conséquent, nous avons également besoin d'une fonction spéciale pour normaliser l'objet Error, c'est-à-dire pour mapper différents noms d'attributs à des noms d'attributs unifiés. Pour des méthodes spécifiques, veuillez vous référer à cet article. Même si les implémentations des navigateurs seront mises à jour, la maintenance manuelle d’une telle table de mappage n’est pas trop difficile.
Similaire au format de trace de pile. Cet attribut enregistre une pile d'informations sur les exceptions sous forme de texte brut. Étant donné que le format de texte utilisé par chaque navigateur est différent, il est également nécessaire de maintenir manuellement une expression régulière pour extraire la fonction de chaque cadre du texte brut. identifiant), fichier (script), numéro de ligne (ligne) et numéro de colonne (colonne).
Restrictions de sécurité
Si vous avez également rencontré une erreur avec le message 'Erreur de script', vous comprendrez de quoi je parle. Il s'agit en fait d'une limitation du navigateur pour les fichiers de script d'origines différentes. La raison de cette restriction de sécurité est la suivante : en supposant que le HTML renvoyé par une banque en ligne après la connexion d'un utilisateur est différent du HTML vu par les utilisateurs anonymes, un site Web tiers peut insérer l'URI de la banque en ligne dans le script. attribut src. Bien entendu, HTML ne peut pas être analysé comme JS, le navigateur lancera donc une exception et le site Web tiers pourra déterminer si l'utilisateur est connecté en analysant l'emplacement de l'exception. Pour cette raison, le navigateur filtre toutes les exceptions levées par les fichiers de script provenant de différentes sources jusqu'à ce qu'il ne reste qu'un seul message inchangé tel que « Erreur de script » et que tous les autres attributs disparaissent.
Pour les sites Web d'une certaine envergure, il est normal que les fichiers scripts soient placés sur un CDN avec des sources différentes. Désormais, même si vous créez votre propre petit site Web, des frameworks courants tels que jQuery et Backbone peuvent directement référencer les versions sur les CDN publics pour accélérer les téléchargements des utilisateurs. Cette restriction de sécurité pose donc quelques problèmes, ce qui fait que les informations d'exception que nous collectons auprès de Chrome et Firefox sont des « erreurs de script » inutiles.
CORS
Pour contourner cette restriction, assurez-vous simplement que le fichier script et la page elle-même ont la même origine. Mais mettre le fichier script sur un serveur qui n'est pas accéléré par CDN ne ralentira pas la vitesse de téléchargement de l'utilisateur ? Une solution consiste à continuer à placer le fichier de script sur le CDN, à utiliser XMLHttpRequest pour télécharger le contenu via CORS, puis à créer une balise <script> Le code intégré dans la page provient bien entendu de la même source. </p>
<p>Cela semble simple, mais il y a de nombreux détails à mettre en œuvre. Pour utiliser un exemple simple : </p>
<p></p>
<div class="codetitle">
<span><a style="CURSOR: pointer" data="85881" class="copybut" id="copybut85881" onclick="doCopy('code85881')"><u>Copier le code</u></a></span> Le code est le suivant :</div>
<div class="codebody" id="code85881">
<br>
<script src="<a href="http://cdn.com/step1.js"></script">http://cdn.com/step1.js"></script</a>><br>
<script><br>
(function step2() {})();<br>
</script>
http://cdn.com/step3.js">>
Si nous disposons déjà d'un ensemble d'outils pour générer des balises <script> pour différentes pages du site Web, nous devons ajuster cet ensemble d'outils pour apporter des modifications aux balises <script>
</p>
<p></p>
<div class="codetitle"><span><a style="CURSOR: pointer" data="68128" class="copybut" id="copybut68128" onclick="doCopy('code68128')">Copier le code<u></u></a> Le code est le suivant :</span></div>
<div class="codebody" id="code68128">
<script><br>
planningRemoteScript('http://cdn.com/step1.js');<br>
</script>
<script><br>
planningInlineScript(code de fonction() {<br>
(function step2() {})();<br>
});<br>
</script>
<script><br>
planningRemoteScript('http://cdn.com/step3.js');<br>
</script>
Nous devons implémenter les deux fonctions planningRemoteScript et planningInlineScript, et nous assurer qu'elles sont définies avant la première balise <script> qui fait référence à un fichier de script externe, puis les balises <script> restantes seront réécrites sous la forme ci-dessus. Notez que la fonction step2 initialement exécutée immédiatement a été placée dans une fonction de code plus grande. La fonction code ne sera pas exécutée, c'est juste un conteneur, afin que le code original de l'étape 2 puisse être conservé sans s'échapper, mais ne sera pas exécuté immédiatement.
<p>Ensuite, nous devons implémenter un mécanisme complet pour garantir que le contenu du fichier téléchargé par planningRemoteScript en fonction de l'adresse et du code directement obtenu par planningInlineScript peut être exécuté l'un après l'autre dans le bon ordre. Je ne donnerai pas le code détaillé ici. Si vous êtes intéressé, vous pouvez l'implémenter vous-même. </p>
<p><strong>Vérification inversée du numéro de ligne</strong></p>
<p>Obtenir du contenu via CORS, puis injecter du code dans la page peut contourner les restrictions de sécurité, mais cela introduira un nouveau problème, à savoir le conflit de numéros de ligne. À l'origine, error.script pouvait être utilisé pour localiser le fichier de script unique, et error.line pouvait être utilisé pour localiser le numéro de ligne unique. Désormais, comme il s'agit de codes intégrés dans la page, plusieurs balises <script> ne peuvent pas être distinguées par error.script. Cependant, le numéro de ligne à l'intérieur de chaque balise <script> être utilisé pour localiser l’emplacement du code source où se trouve l’erreur. </p>
<p>Afin d'éviter les conflits de numéros de ligne, nous pouvons gaspiller certains numéros de ligne afin que les plages de numéros de ligne utilisées par le code réel dans chaque balise <script> Par exemple, en supposant que le code réel de chaque balise <script> ne dépasse pas 1 000 lignes, je peux alors laisser le code de la première balise <script> occuper les lignes 1 à 1 000 et laisser la seconde. La balise <script> occupe les lignes 1 001 à 2 000 (insérez 1 000 lignes vides avant) et le code de la troisième balise <script> occupe les lignes 2 001 à 3 000 (insérez 2 000 lignes vides avant). Ensuite, nous utilisons les attributs data-* pour enregistrer ces informations afin de les récupérer facilement. </p>
<p></p>
<div class="codetitle">
<span><a style="CURSOR: pointer" data="94831" class="copybut" id="copybut94831" onclick="doCopy('code94831')"><u>Copier le code</u></a></span> Le code est le suivant :</div>
<div class="codebody" id="code94831">
<br>
<script<br />
data-src="<a href="http://cdn.com/step1.js">http://cdn.com/step1.js</a>"<br />
data-line-start="1"<br />
><br>
// code pour l'étape 1<br>
</script>
data-src="http://cdn.com/step3.js"
data-line-start="2001"
>
// 'n' *2000
// code pour l'étape 3
Bien sûr, puisque nous ne pouvons pas garantir que chaque fichier de script ne contient que 1 000 lignes et que certains fichiers de script peuvent contenir nettement moins de 1 000 lignes, il n'est pas nécessaire d'attribuer une plage fixe de 1 000 lignes à chaque balise