Comment capturer et analyser l'erreur JavaScript
Les ingénieurs frontaux savent tous que JavaScript possède des capacités de gestion d'exception de base. Nous pouvons lancer une nouvelle erreur (), et le navigateur lèvera également une exception lorsque nous appelons l'erreur de l'API. Mais on estime que la plupart des ingénieurs frontaux n'ont pas envisagé de collecter ces informations exceptionnelles. Quoi qu'il en soit, tant que le rafraîchissement ne se reproduit pas après une erreur JavaScript, l'utilisateur peut résoudre le problème en rafraîchissant, et le navigateur ne se bloquera pas, et ce sera bien si cela ne s'est pas produit. Cette hypothèse était vraie avant que l'application à page unique ne devienne populaire. L'application actuelle de la page est extrêmement complexe après avoir fonctionné pendant une période de temps. Ne retravougeriez-vous pas complètement les opérations précédentes? Il nous est donc toujours nécessaire de capturer et d'analyser ces informations d'exception, puis nous pouvons modifier le code pour éviter d'affecter l'expérience utilisateur.
Comment prendre des exceptions
Nous nous sommes écrits à lancer une nouvelle erreur (). Cependant, les exceptions qui se produisent lors de l'appel de l'API du navigateur ne sont pas nécessairement si faciles à attraper. Pour le premier, nous pouvons également l'attraper grâce à Try-Catch, pour le second, nous devons écouter des exceptions mondiales, puis l'attraper.
coup de passe
Si certaines API du navigateur sont connues pour lancer des exceptions, nous devons mettre l'appel dans un coup de main pour éviter que l'ensemble du programme entre dans un état illégal en raison d'erreurs. Par exemple, Window.Localstorage est une telle API.
La copie de code est la suivante:
essayer {
localStorage.SetItem ('Date', date.Now ());
} catch (erreur) {
reporterror (erreur);
}
Un autre scénario applicable de couple d'essai courant est les rappels. Parce que le code de la fonction de rappel est incontrôlable, nous ne savons pas à quel point le code est bon et si d'autres API qui lancent des exceptions seront appelées. Afin de ne pas faire exécuter d'autres codes après avoir appelé le rappel en raison d'erreurs de rappel, il est nécessaire de remettre le rappel dans un coup de main.
La copie de code est la suivante:
auditeurs.ForEach (fonction (auditeur) {
essayer {
auditeur();
} catch (erreur) {
reporterror (erreur);
}
});
fenêtre.
Pour les endroits que TRY-Catch ne peut pas couvrir, si une exception se produit, elle ne peut être capturée que via Window.onerror.
La copie de code est la suivante:
window.onerror =
fonction (errorMessage, scripturi, linenumber) {
reporterror ({
Message: errorMessage,
script: scripturi,
Ligne: Livreau en line
});
}
Faites attention à ne pas être intelligent et à utiliser Window.AddeventListener ou Window.Attachevent pour écouter Window.onerror. De nombreux navigateurs implémentent uniquement Window.onerror, ou uniquement Window.Ilerror l'implémentation est standard. Étant donné que le projet standard définit également Window.onerror, nous avons juste besoin d'utiliser Window.onerror.
Propriété manquante
Supposons que nous ayons une fonction Reporterror pour collecter des exceptions capturées, puis les envoyer par lots au stockage côté serveur pour la requête et l'analyse, quelles informations voulons-nous collecter? Des informations plus utiles incluent: type d'erreur (nom), message d'erreur (message), adresse de fichier de script (script), numéro de ligne (ligne), numéro de colonne (colonne) et trace de pile. Si une exception est capturée par l'essai, toutes ces informations sont sur l'objet d'erreur (pris en charge par les navigateurs grand public), donc Reporterror peut également collecter ces informations. Mais s'il est capturé via Window.onerror, nous savons tous que cette fonction d'événement n'a que 3 paramètres, donc des informations autres que ces 3 paramètres sont perdus.
Sérialiser les messages
Si l'objet d'erreur est créé par nous-mêmes, Error.Message est contrôlé par nous. Fondamentalement, ce que nous mettons dans l'erreur.Message, et quel sera le premier paramètre (message) de Window.onerror. (Le navigateur apporte en fait un peu de modification, comme l'ajout de l'erreur non apportée: 'Préfixe.) Par conséquent, nous pouvons sérialiser les attributs qui nous préoccupent (comme JSON.Strinify) et les stocker dans Error.Message, puis lire eux dans Window.onerror, retirez-le et désérialisez-le. Bien sûr, cela est limité à l'objet d'erreur que nous avons créé nous-mêmes.
Le cinquième paramètre
Les fabricants de navigateurs connaissent également les restrictions auxquelles les gens sont soumis lors de l'utilisation de Window.onerror, afin qu'ils commencent à ajouter de nouveaux paramètres à Window.onerror. Étant donné que seuls les numéros de ligne et les numéros de colonne ne semblent être très symétriques, c'est-à-dire d'abord ajouté les numéros de colonne et les a placés dans le quatrième paramètre. Cependant, ce qui est plus préoccupé par tout le monde, c'est de savoir s'il peut obtenir la pile complète, alors Firefox a dit qu'il serait préférable de mettre la pile dans le cinquième paramètre. Mais Chrome a dit qu'il serait préférable de mettre l'intégralité de l'objet d'erreur dans le cinquième paramètre. En conséquence, parce que Chrome est plus rapide, une nouvelle fenêtre. La signature d'onserror a été implémentée dans Chrome 30, ce qui a entraîné la rédaction suivante du projet standard.
La copie de code est la suivante:
window.onerror = fonction (
ErrorMessage,
scripturi,
Livre de linge,
ColumnNumber,
erreur
) {
if (error) {
reporterror (erreur);
} autre {
reporterror ({
Message: errorMessage,
script: scripturi,
Ligne: Linen Dumber,
colonne: colonne
});
}
}
Régularité des attributs
Les noms des propriétés d'objet d'erreur que nous avons discutées auparavant sont basées sur des méthodes de dénomination chromées. Par conséquent, nous avons également besoin d'une fonction spéciale pour normaliser l'objet d'erreur, c'est-à-dire pour cartographier différents noms d'attribut à un nom d'attribut unifié. Pour des pratiques spécifiques, veuillez vous référer à cet article. Bien que la mise en œuvre du navigateur soit mise à jour, il ne sera pas trop difficile pour les humains de maintenir une telle table de cartographie.
Le format de trace de pile similaire est similaire. Cette propriété enregistre les informations de pile d'une exception lorsqu'elle se produit sous la forme de texte brut. Nom (identifiant), fichier (script), ligne (ligne) et colonne (colonne).
Limitations de sécurité
Si vous avez également rencontré une erreur avec le message «Erreur de script». La raison de cette restriction de sécurité est la suivante: Supposons que le HTML renvoyé par un banquier en ligne après la connexion soit différent du HTML vu par un utilisateur anonyme, un site Web tiers peut mettre l'URI de cette banque en ligne dans le script. Attribut SRC. Bien sûr, HTML ne peut pas être analysé comme JS, donc le navigateur lancera une exception, et ce site Web tiers peut déterminer si l'utilisateur est connecté en analysant l'emplacement de l'exception. Pour cette raison, le navigateur filtre toutes les exceptions lancées par différents fichiers de script source, ne laissant qu'un message inchangé comme «erreur de script»., Et tous les autres attributs disparaissent.
Pour les sites Web d'une certaine échelle, il est normal que les fichiers de script soient placés sur les CDN et différentes sources sont placées. Maintenant, même si vous créez vous-même un petit site Web, des frameworks communs tels que jQuery et Backbone peuvent directement référencer la version sur le CDN public pour accélérer les téléchargements des utilisateurs. Ainsi, cette restriction de sécurité cause des problèmes, provoquant des informations d'exception que nous collectons auprès de Chrome et de Firefox comme une «erreur de script» inutile. ».
Cors
Si vous souhaitez contourner cette restriction, assurez-vous simplement que le fichier de script et la page eux-mêmes sont les mêmes. Mais la mise en place de fichiers de script sur des serveurs qui ne sont pas accélérés par CDN ne réduiraient-ils 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 <cript> pour l'injecter dans la page. Le code intégré dans la page est bien sûr la même origine.
C'est simple à dire, mais il y a beaucoup de détails à mettre en œuvre. Pour donner un exemple simple:
La copie de code est la suivante:
<script src = "http://cdn.com/step1.js"> </ script>
<cript>
(fonction Step2 () {}) ();
</cript>
<script src = "http://cdn.com/step3.js"> </ script>
Nous savons tous que s'il y a des dépendances dans Step1, Step2 et Step3, elle doit être exécutée strictement dans cet ordre, sinon une erreur peut se produire. Le navigateur peut demander des fichiers Step1 et Step3 en parallèle, mais la commande est garantie lorsqu'elle est exécutée. Si nous obtenons le contenu du fichier de Step1 et Step3 en utilisant XMLHttpRequest, nous devons assurer l'ordre correct par nous-mêmes. De plus, n'oubliez pas que Step2.
Si nous avons déjà un ensemble complet d'outils pour générer des balises <cript> pour différentes pages sur le site Web, nous devons ajuster cet ensemble d'outils pour apporter des modifications aux balises <cript>:
La copie de code est la suivante:
<cript>
SchedUleMotescript ('http://cdn.com/step1.js');
</cript>
<cript>
ScheduleInlinescript (Code de fonction () {
(fonction Step2 () {}) ();
});
</cript>
<cript>
SchedUleMotescript ('http://cdn.com/step3.js');
</cript>
Nous devons implémenter les deux fonctions scheduleReMoScript et calendrierInlinescript, et nous assurer qu'elles sont définies avant la première balise <cript> qui fait référence au fichier de script externe, puis les balises <cript> restantes seront réécrites dans le formulaire ci-dessus. Notez que la fonction Step2 qui a été exécutée a été immédiatement placée dans une fonction de code plus grande. La fonction de code ne sera pas exécutée, ce n'est qu'un conteneur, afin que le code Step2 d'origine puisse être conservé sans s'échapper, mais il ne sera pas exécuté immédiatement.
Ensuite, nous devons implémenter un mécanisme complet pour nous assurer que le contenu de fichier téléchargé par ScheduleReMoScript en fonction de l'adresse et du code directement obtenu par scheduleInscript peuvent être exécutés un par un dans le bon ordre. Je ne donnerai pas le code détaillé ici.
Vérification du numéro de ligne
Obtenir du contenu via CORS et injecter du code dans la page peut percer les restrictions de sécurité, mais il introduira un nouveau problème, c'est-à-dire les conflits de numéro de ligne. À l'origine, le fichier de script unique pourrait être situé via Error.Script, puis le numéro de ligne unique pourrait être situé via Error.line. Maintenant, comme ils sont tous des codes intégrés dans la page, plusieurs balises <cript> ne peuvent pas être distinguées par error.script. .
Pour éviter les conflits de numéro de ligne, nous pouvons gaspiller certains numéros de ligne afin que les intervalles de numéro de ligne utilisé par le code réel dans chaque balise <cript> ne se chevauchent pas. Par exemple, en supposant que le code réel dans chaque balise <cript> ne dépasse pas 1000 lignes, je peux faire le code dans la première balise <script> TAGE UP UP LINE 11000 et le deuxième <script> TAG Le code dans le code occupe la ligne 10012000 (1000 lignes vides ont été insérées avant l'insertion), le code de la troisième balise <cript> occupe la ligne 20013000 (2000 lignes vides ont été insérées avant l'insertion), etc. Ensuite, nous utilisons l'attribut Data- * pour enregistrer ces informations pour une vérification simple.
La copie de code est la suivante:
<script
data-src = "http://cdn.com/step1.js"
data-line-start = "1"
>
// code pour l'étape 1
</cript>
<script data-line-start = "1001">
// '/ n' * 1000
// code pour l'étape 2
</cript>
<script
data-src = "http://cdn.com/step3.js"
data-line-start = "2001"
>
// '/ n' * 2000
// code pour l'étape 3
</cript>
Après ce traitement, si une erreur d'erreur est 3005, cela signifie que l'erreur réelle.script doit être «http://cdn.com/step3.js», tandis que l'erreur réelle.Leline doit être 5 .. Nous pouvons compléter ce numéro de ligne Vérification inverse de la fonction Reporterror mentionnée précédemment.
Bien sûr, comme nous ne pouvons pas garantir que chaque fichier de script n'a que 1000 lignes, il est également possible que certains fichiers de script soient nettement moins de 1000 lignes, il n'est donc pas nécessaire d'allouer correctement un intervalle de 1000 ligne à chaque balise <cript>. Nous pouvons allouer des intervalles en fonction du nombre réel de lignes de script, nous assurer que les intervalles utilisés par chaque balise <cript> ne se chevauchent pas.
Attribut de crossorigin
Les restrictions de sécurité imposées par les navigateurs sur le contenu de différentes sources ne sont bien sûr pas limitées aux balises <cript>. Étant donné que XMLHttpRequest peut percer cette limitation par le biais de COR, pourquoi les ressources sont-elles directement référencées par le biais de balises non autorisées? C'est certainement OK.
La limitation de la référence à différents fichiers de script source pour les balises <cript> s'applique également à la référence à différents fichiers d'image source pour les balises <MG>. Si une balise <MG> est une source différente, une fois utilisée dans le dessin <Canvas>, le <Canvas> deviendra un état d'écriture uniquement, garantissant que le site Web ne peut pas voler des données d'image non autorisées à partir de différentes sources via JavaScript. Plus tard, la balise <MG> a résolu ce problème en introduisant l'attribut Crossorigin. Si Crossorigin = "Anonymous" est équivalent aux CORS anonymes;
Puisque la balise <Mg> peut le faire, pourquoi la balise <cript> ne peut-elle pas faire cela? Le fabricant du navigateur a donc ajouté le même attribut de crossorigin à la balise <cript> pour résoudre les restrictions de sécurité ci-dessus. Maintenant, le support Chrome et Firefox pour cette propriété est entièrement gratuit. Safari traitera Crossorigin = "Anonymous" comme Crossorigin = "Use-Credentials", et le résultat est que si le serveur ne prend en charge que les COR anonymes, Safari traitera l'authentification comme un échec. Étant donné que le serveur CDN est conçu pour renvoyer uniquement le contenu statique pour des raisons de performances, il est impossible de renvoyer dynamiquement l'en-tête HTTP requis pour authentifier les COR en fonction des demandes.
Résumer
La manipulation des exceptions JavaScript semble simple et n'est pas différente des autres langues, mais il n'est pas si facile d'attraper toutes les exceptions et d'analyser les propriétés. Bien que certains services tiers fournissent désormais des services Google Analytics qui captent des exceptions JavaScript, si vous souhaitez comprendre les détails et les principes, vous devez le faire vous-même.