Ce README contient des informations que j'ai apprises au fil des années sur la gestion des erreurs JavaScript, leur signalement au serveur et la navigation parmi de nombreux bugs qui peuvent rendre tout cela très difficile. Les navigateurs se sont améliorés dans ce domaine, mais il reste encore des progrès à faire pour garantir que toutes les applications peuvent gérer sainement et correctement toute erreur qui se produit.
Les cas de test pour le contenu trouvé dans ce guide sont disponibles sur https://mknichel.github.io/javascript-errors/.
Table des matières
Introduction
Anatomie d'une erreur JavaScript
Générer une erreur JavaScript
Messages d'erreur
Format de trace de pile
Détecter les erreurs JavaScript
fenêtre.onerror
essayer/attraper
Points d'entrée protégés
Promesses
Travailleurs du Web
Extensions Chrome
La détection, le signalement et la correction des erreurs constituent une partie importante de toute application pour garantir la santé et la stabilité de l'application. Étant donné que le code JavaScript est également exécuté sur le client et dans de nombreux environnements de navigateur différents, rester au courant des erreurs JS de votre application peut également être difficile. Il n'existe aucune spécification Web formelle sur la manière de signaler les erreurs JS, ce qui entraîne des différences dans l'implémentation de chaque navigateur. De plus, il y a eu de nombreux bugs dans la mise en œuvre des erreurs JavaScript par les navigateurs, ce qui a rendu cela encore plus difficile. Cette page parcourt ces aspects des erreurs JS afin que les futurs développeurs puissent mieux gérer les erreurs et que les navigateurs convergent, espérons-le, vers des solutions standardisées.
Une erreur JavaScript est composée de deux éléments principaux : le message d'erreur et la trace de la pile . Le message d'erreur est une chaîne qui décrit ce qui ne va pas, et la trace de pile décrit où dans le code l'erreur s'est produite. Les erreurs JS peuvent être produites soit par le navigateur lui-même, soit par le code de l'application.
Une erreur JS peut être générée par le navigateur lorsqu'un morceau de code ne s'exécute pas correctement, ou elle peut être générée directement par le code.
Par exemple:
var une = 3; une ();
Dans cet exemple, une variable qui est en réalité un nombre ne peut pas être invoquée en tant que fonction. Le navigateur générera une erreur du type TypeError: a is not a function
avec une trace de pile qui pointe vers cette ligne de code.
Un développeur peut également souhaiter générer une erreur dans un morceau de code si une certaine condition préalable n'est pas remplie. Par exemple
si (!checkPrecondition()) { throw new Error("Ne répond pas aux conditions préalables !");}
Dans ce cas, l'erreur sera Error: Doesn't meet precondition!
. Cette erreur contiendra également une trace de pile qui pointe vers la ligne appropriée. Les erreurs générées par le navigateur et le code de l'application peuvent être traitées de la même manière.
Les développeurs peuvent générer une erreur de plusieurs manières en JavaScript :
throw new Error('Problem description.')
throw Error('Problem description.')
<-- équivalent au premier
throw 'Problem description.'
<-- mauvais
throw null
<-- encore pire
Lancer une chaîne ou une valeur nulle n'est vraiment pas recommandé car le navigateur n'attachera pas de trace de pile à cette erreur, perdant ainsi le contexte de l'endroit où cette erreur s'est produite dans le code. Il est préférable de lancer un véritable objet Error, qui contiendra le message d'erreur ainsi qu'une trace de pile qui pointe vers les bonnes lignes de code où l'erreur s'est produite.
Chaque navigateur possède son propre ensemble de messages qu'il utilise pour les exceptions intégrées, comme l'exemple ci-dessus pour tenter d'appeler une non-fonction. Les navigateurs essaieront d'utiliser les mêmes messages, mais comme il n'y a aucune spécification, cela n'est pas garanti. Par exemple, Chrome et Firefox utilisent {0} is not a function
pour l'exemple ci-dessus tandis qu'IE11 signalera Function expected
(notamment également sans signaler quelle variable a été tentée d'être appelée).
Cependant, les navigateurs ont également tendance à diverger souvent. Lorsqu'il y a plusieurs instructions par défaut dans une instruction switch
, Chrome lancera "More than one default clause in switch statement"
tandis que Firefox signalera "more than one switch default"
. À mesure que de nouvelles fonctionnalités sont ajoutées au Web, ces messages d'erreur doivent être mis à jour. Ces différences peuvent entrer en jeu plus tard lorsque vous essayez de gérer les erreurs signalées par du code obscurci.
Vous pouvez trouver les modèles que les navigateurs utilisent pour les messages d'erreur à l'adresse :
Firefox - http://mxr.mozilla.org/mozilla1.9.1/source/js/src/js.msg
Chrome - https://code.google.com/p/v8/source/browse/branches/bleeding_edge/src/messages.js
Internet Explorer - https://github.com/Microsoft/ChakraCore/blob/4e4d4f00f11b2ded23d1885e85fc26fcc96555da/lib/Parser/rterrors.h
Les navigateurs produiront différents messages d'erreur pour certaines exceptions.
La trace de pile est une description de l'endroit où l'erreur s'est produite dans le code. Il est composé d'une série de frames, où chaque frame décrit une ligne particulière du code. L'image la plus haute est l'emplacement où l'erreur a été générée, tandis que les images suivantes sont la pile d'appels de fonction - ou comment le code a été exécuté pour arriver à ce point où l'erreur a été générée. Étant donné que JavaScript est généralement concaténé et réduit, il est également important d'avoir des numéros de colonnes afin que l'instruction exacte puisse être localisée lorsqu'une ligne donnée contient une multitude d'instructions.
Une trace de pile de base dans Chrome ressemble à :
at throwError (http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:9) at http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3
Chaque cadre de pile se compose d'un nom de fonction (le cas échéant et si le code n'a pas été exécuté dans la portée globale), du script dont il provient et du numéro de ligne et de colonne du code.
Malheureusement, il n'existe pas de norme pour le format de trace de pile, cela diffère donc selon le navigateur.
La trace de pile de Microsoft Edge et IE 11 ressemble à celle de Chrome, sauf qu'elle répertorie explicitement le code global :
at throwError (http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:3) at Global code (http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3)
La trace de la pile de Firefox ressemble à :
throwError@http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:9 @http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3
Le format de Safari est similaire à celui de Firefox mais est également légèrement différent :
throwError@http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:18 global code@http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:13
Les mêmes informations de base sont présentes, mais le format est différent.
Notez également que dans l'exemple Safari, outre le format différent de celui de Chrome, les numéros de colonne sont différents de ceux de Chrome et de Firefox. Les numéros de colonnes peuvent également différer davantage dans différentes situations d'erreur - par exemple dans le code (function namedFunction() { throwError(); })();
, Chrome signalera la colonne pour l'appel de fonction throwError()
tandis qu'IE11 signalera le numéro de colonne comme début de la chaîne. Ces différences reviendront en jeu plus tard lorsque le serveur devra analyser la trace de pile pour détecter les erreurs signalées et désobscurcir les traces de pile obscurcies.
Voir https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack pour plus d'informations sur la propriété de pile des erreurs. Lors de l'accès à la propriété Error.stack, Chrome inclut le message d'erreur dans la pile, mais pas Safari 10+.
Le format des traces de pile est différent selon le navigateur en termes de forme et de numéros de colonnes utilisés.
En approfondissant davantage, il existe de nombreuses nuances pour empiler les formats de trace qui sont abordées dans les sections ci-dessous.
Par défaut, les fonctions anonymes n'ont pas de nom et apparaissent soit sous forme de chaîne vide, soit sous forme de "Fonction anonyme" dans les noms de fonctions dans la trace de la pile (selon le navigateur). Pour améliorer le débogage, vous devez ajouter un nom à toutes les fonctions pour garantir qu'elles apparaissent dans le cadre de la pile. Le moyen le plus simple de procéder consiste à garantir que les fonctions anonymes sont spécifiées avec un nom, même si ce nom n'est utilisé nulle part ailleurs. Par exemple:
setTimeout(function nameOfTheAnonymousFunction() { ... }, 0);
Cela fera passer la trace de la pile de :
at http://mknichel.github.io/javascript-errors/javascript-errors.js:125:17
à
at nameOfTheAnonymousFunction (http://mknichel.github.io/javascript-errors/javascript-errors.js:121:31)
Dans Safari, cela viendrait de :
https://mknichel.github.io/javascript-errors/javascript-errors.js:175:27
à
nameOfTheAnonymousFunction@https://mknichel.github.io/javascript-errors/javascript-errors.js:171:41
Cette méthode garantit que nameOfTheAnonymousFunction
apparaît dans le cadre de tout code provenant de cette fonction, ce qui rend le débogage beaucoup plus facile. Voir http://www.html5rocks.com/en/tutorials/developertools/async-call-stack/#toc-debugging-tips pour plus d'informations.
Les navigateurs utiliseront également le nom de la variable ou de la propriété à laquelle une fonction est affectée si la fonction elle-même n'a pas de nom. Par exemple, dans
var fnVariableName = function() { ... };
les navigateurs utiliseront fnVariableName
comme nom de la fonction dans les traces de pile.
at throwError (http://mknichel.github.io/javascript-errors/javascript-errors.js:27:9) at fnVariableName (http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37)
Plus nuancé encore, si cette variable est définie au sein d'une autre fonction, tous les navigateurs utiliseront uniquement le nom de la variable comme nom de la fonction dans la trace de la pile sauf Firefox, qui utilisera une forme différente qui concatène le nom de la fonction externe avec le nom de la variable interne. Exemple:
fonction throwErrorFromInnerFunctionAssignedToVariable() { var fnVariableName = function() { throw new Error("foo"); } ; fnVariableName();}
produira dans Firefox :
throwErrorFromInnerFunctionAssignedToVariable/fnVariableName@http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37
Dans d'autres navigateurs, cela ressemblerait à :
at fnVariableName (http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37)
Firefox utilise un texte de cadre de pile différent pour les fonctions définies dans une autre fonction.
Le nom d'affichage d'une fonction peut également être défini par la propriété displayName
dans tous les principaux navigateurs à l'exception d'IE11. Dans ces navigateurs, le displayName apparaîtra dans le débogueur devtools, mais dans tous les navigateurs sauf Safari, il ne sera pas utilisé dans les traces de pile d'erreurs (Safari diffère des autres en utilisant également le displayName dans la trace de pile associée à une erreur).
var someFunction = function() {};someFunction.displayName = " # Une description plus longue de la fonction.";
Il n'existe pas de spécification officielle pour la propriété displayName, mais elle est prise en charge par tous les principaux navigateurs. Voir https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/displayName et http://www.alertdebugging.com/2009/04/29/building-a-better -javascript-profiler-with-webkit/ pour plus d'informations sur displayName.
IE11 ne prend pas en charge la propriété displayName.
Safari utilise la propriété displayName comme nom de symbole dans les traces de la pile d'erreurs.
Si une erreur est signalée sans trace de pile (voir plus de détails ci-dessous), il est alors possible de capturer par programme une trace de pile.
Dans Chrome, cela est très simple à faire en utilisant l'API Error.captureStackTrace
. Voir https://github.com/v8/v8/wiki/Stack%20Trace%20API pour plus d'informations sur l'utilisation de cette API.
Par exemple:
fonction ignoreThisFunctionInStackTrace() { var err = nouvelle erreur (); Error.captureStackTrace(err, ignoreThisFunctionInStackTrace); renvoie err.stack;}
Dans d'autres navigateurs, une trace de pile peut également être collectée en créant une nouvelle erreur et en accédant à la propriété de pile de cet objet :
var err = new Error('');return err.stack;
Cependant, IE10 ne remplit la trace de la pile que lorsque l'erreur est réellement générée :
essayer { throw new Error('');} catch (e) { retourner e.stack ;}
Si aucune de ces approches ne fonctionne, il est alors possible de créer une trace de pile approximative sans numéros de ligne ni colonnes en itérant sur l'objet arguments.callee.caller
- cela ne fonctionnera cependant pas en mode strict ES5 et ce n'est pas une approche recommandée.
Il est très courant que des points asynchrones soient insérés dans le code JavaScript, par exemple lorsque le code utilise setTimeout
ou via l'utilisation de Promises. Ces points d'entrée asynchrones peuvent poser des problèmes pour les traces de pile, car ils provoquent la formation d'un nouveau contexte d'exécution et la trace de pile recommence à zéro.
Chrome DevTools prend en charge les traces de pile asynchrone, ou en d'autres termes, s'assure que la trace de pile d'une erreur affiche également les images qui se sont produites avant l'introduction du point asynchrone. Avec l'utilisation de setTimeout, cela permettra de capturer qui a appelé la fonction setTimeout qui a finalement produit une erreur. Voir http://www.html5rocks.com/en/tutorials/developertools/async-call-stack/ pour plus d'informations.
Une trace de pile asynchrone ressemblera à :
throwError @ throw-error.js:2 setTimeout (async) throwErrorAsync @ throw-error.js:10 (anonymous function) @ throw-error-basic.html:14
Les traces de pile asynchrone ne sont actuellement prises en charge que dans Chrome DevTools, uniquement pour les exceptions levées lorsque les DevTools sont ouverts. Les traces de pile accessibles à partir des objets Error dans le code ne contiendront pas la trace de pile asynchrone.
Il est possible de polyfiller les traces de pile asynchrones dans certains cas, mais cela pourrait entraîner une baisse significative des performances de votre application, car la capture d'une trace de pile n'est pas bon marché.
Seul Chrome DevTools prend en charge nativement les traces de pile asynchrone.
Les traces de pile pour le code qui a été évalué ou intégré dans une page HTML utiliseront l'URL de la page et les numéros de ligne/colonne pour le code exécuté.
Par exemple:
at throwError (http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:9) at http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3
Si ces scripts proviennent en réalité d'un script qui a été intégré pour des raisons d'optimisation, alors les numéros d'URL, de ligne et de colonne seront erronés. Pour contourner ce problème, Chrome et Firefox prennent en charge l'annotation //# sourceURL=
(ce qui n'est pas le cas de Safari, Edge et IE). L'URL spécifiée dans cette annotation sera utilisée comme URL pour toutes les traces de pile, et le numéro de ligne et de colonne sera calculé par rapport au début de la balise <script>
au lieu du document HTML. Pour la même erreur que ci-dessus, l'utilisation de l'annotation sourceURL avec la valeur « inline.js » produira une trace de pile qui ressemble à :
at throwError (http://mknichel.github.io/javascript-errors/inline.js:8:9) at http://mknichel.github.io/javascript-errors/inline.js:12:3
Il s'agit d'une technique très pratique pour garantir que les traces de pile sont toujours correctes même lors de l'utilisation de scripts en ligne et d'évaluation.
http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl décrit l'annotation sourceURL plus en détail.
Safari, Edge et IE ne prennent pas en charge l'annotation sourceURL pour nommer les scripts et les évaluations en ligne. Si vous utilisez des scripts en ligne dans IE ou Safari et que vous masquez votre code, vous ne pourrez pas masquer les erreurs provenant de ces scripts.
Jusqu'à Chrome 42, Chrome ne calculait pas correctement les numéros de ligne pour les scripts en ligne qui utilisent l'annotation sourceURL. Voir https://bugs.chromium.org/p/v8/issues/detail?id=3920 pour plus d'informations.
Les numéros de ligne pour les cadres de pile des scripts en ligne sont incorrects lorsque l'annotation sourceURL est utilisée car ils sont relatifs au début du document HTML au lieu du début de la balise de script en ligne (ce qui rend impossible une désobscurcissement correcte). https://code.google.com/p/chromium/issues/detail?id=578269
Pour le code qui utilise eval, il existe d'autres différences dans la trace de pile, outre le fait qu'il utilise ou non l'annotation sourceURL. Dans Chrome, une trace de pile provenant d'une instruction utilisée dans eval pourrait ressembler à :
Error: Error from eval at evaledFunction (eval at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3), <anonymous>:1:36) at eval (eval at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3), <anonymous>:1:68) at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3)
Dans MS Edge et IE11, cela ressemblerait à :
Error from eval at evaledFunction (eval code:1:30) at eval code (eval code:1:2) at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3)
Dans Safari :
Error from eval evaledFunction eval code eval@[native code] evalError@http://mknichel.github.io/javascript-errors/javascript-errors.js:137:7
et dans Firefox :
Error from eval evaledFunction@http://mknichel.github.io/javascript-errors/javascript-errors.js line 137 > eval:1:36 @http://mknichel.github.io/javascript-errors/javascript-errors.js line 137 > eval:1:11 evalError@http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3
Ces différences peuvent rendre difficile l’analyse identique du code d’évaluation dans tous les navigateurs.
Chaque navigateur utilise un format de trace de pile différent pour les erreurs survenues dans eval.
Votre code JavaScript peut également être appelé directement depuis le code natif. Array.prototype.forEach
est un bon exemple : vous transmettez une fonction à forEach
et le moteur JS appellera cette fonction pour vous.
fonction throwErrorWithNativeFrame() { vararr = [0, 1, 2, 3]; arr.forEach(fonction nomméeFn(valeur) {throwError(); });}
Cela produit différentes traces de pile dans différents navigateurs. Chrome et Safari ajoutent le nom de la fonction native dans la trace de pile elle-même en tant que cadre distinct, tel que :
(Chrome) at namedFn (http://mknichel.github.io/javascript-errors/javascript-errors.js:153:5) at Array.forEach (native) at throwErrorWithNativeFrame (http://mknichel.github.io/javascript-errors/javascript-errors.js:152:7) (Safari) namedFn@http://mknichel.github.io/javascript-errors/javascript-errors.js:153:15 forEach@[native code] throwErrorWithNativeFrame@http://mknichel.github.io/javascript-errors/javascript-errors.js:152:14 (Edge) at namedFn (http://mknichel.github.io/javascript-errors/javascript-errors.js:153:5) at Array.prototype.forEach (native code) at throwErrorWithNativeFrame (http://mknichel.github.io/javascript-errors/javascript-errors.js:152:7)
Cependant, Firefox et IE11 n'indiquent pas que forEach
a été appelé dans le cadre de la pile :
namedFn@http://mknichel.github.io/javascript-errors/javascript-errors.js:153:5 throwErrorWithNativeFrame@http://mknichel.github.io/javascript-errors/javascript-errors.js:152:3
Certains navigateurs incluent des cadres de code natifs dans les traces de pile, tandis que d'autres ne le font pas.
Pour détecter que votre application a eu une erreur, certains codes doivent être capables de détecter cette erreur et d'en rendre compte. Il existe plusieurs techniques pour détecter les erreurs, chacune avec ses avantages et ses inconvénients.
window.onerror
est l’un des moyens les plus simples et les meilleurs pour commencer à détecter les erreurs. En attribuant window.onerror
à une fonction, toute erreur non détectée par une autre partie de l'application sera signalée à cette fonction, ainsi que des informations sur l'erreur. Par exemple:
window.onerror = function(msg, url, ligne, col, err) { console.log('L'application a rencontré une erreur : ' + msg); console.log('Trace de la pile : ' + err.stack);}
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror décrit cela plus en détail.
Historiquement, cette approche a posé quelques problèmes :
Aucun objet d'erreur fourni
Le 5ème argument de la fonction window.onerror
est censé être un objet Error. Cela a été ajouté à la spécification WHATWG en 2013 : https://html.spec.whatwg.org/multipage/webappapis.html#errorevent. Chrome, Firefox et IE11 fournissent désormais correctement un objet Error (ainsi que la propriété de pile critique), mais pas Safari, MS Edge et IE10. Cela fonctionne dans Firefox depuis Firefox 14 (https://bugzilla.mozilla.org/show_bug.cgi?id=355430) et dans Chrome depuis fin 2013 (https://mikewest.org/2013/08/debugging-runtime-errors -avec-window-onerror, https://code.google.com/p/chromium/issues/detail?id=147127). Safari 10 a lancé la prise en charge de l'objet Error dans window.onerror.
Safari (versions inférieures à 10), MS Edge et IE10 ne prennent pas en charge un objet Error avec une trace de pile dans window.onerror.
Désinfection inter-domaines
Dans Chrome, les erreurs provenant d'un autre domaine dans le gestionnaire window.onerror seront corrigées en "Erreur de script.", "", 0. Ceci est généralement correct si vous ne voulez vraiment pas traiter l'erreur si elle provient d'un script qui ne vous intéresse pas, afin que l'application puisse filtrer les erreurs qui ressemblent à ceci. Cependant, cela ne se produit pas dans Firefox, Safari ou IE11, et Chrome ne le fait pas non plus pour les blocs try/catch qui encapsulent le code incriminé.
Si vous souhaitez recevoir des erreurs dans window.onerror
dans Chrome avec une fidélité totale à partir de scripts inter-domaines, ces ressources doivent fournir les en-têtes d'origine croisée appropriés. Voir https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror pour plus d'informations.
Chrome est le seul navigateur qui nettoiera les erreurs provenant d'une autre origine. Prenez soin de les filtrer ou de définir les en-têtes appropriés.
Extensions Chrome
Dans les anciennes versions de Chrome, les extensions Chrome installées sur la machine d'un utilisateur pouvaient également générer des erreurs signalées à window.onerror. Ce problème a été corrigé dans les versions plus récentes de Chrome. Consultez la section dédiée aux extensions Chrome ci-dessous.
L'API window.addEventListener("error")
fonctionne de la même manière que l'API window.onerror. Voir http://www.w3.org/html/wg/drafts/html/master/webappapis.html#runtime-script-errors pour plus d'informations sur cette approche.
La détection des erreurs via window.onerror n'empêche pas cette erreur d'apparaître également dans la console DevTools. Il s’agit probablement du bon comportement pour le développement puisque le développeur peut facilement voir l’erreur. Si vous ne souhaitez pas que ces erreurs apparaissent en production pour les utilisateurs finaux, e.preventDefault()
peut être appelée si vous utilisez l'approche window.addEventListener.
window.onerror est le meilleur outil pour détecter et signaler les erreurs JS. Il est recommandé que seules les erreurs JS avec des objets Error valides et des traces de pile soient signalées au serveur, sinon les erreurs peuvent être difficiles à enquêter ou vous risquez de recevoir beaucoup de spam provenant d'extensions Chrome ou de scripts inter-domaines.
Compte tenu de la section ci-dessus, il n'est malheureusement pas possible de s'appuyer sur window.onerror
dans tous les navigateurs pour capturer toutes les informations sur les erreurs. Pour intercepter les exceptions localement, un bloc try/catch est le choix évident. Il est également possible d'envelopper des fichiers JavaScript entiers dans un bloc try/catch pour capturer les informations d'erreur qui ne peuvent pas être interceptées avec window.onerror. Cela améliore la situation pour les navigateurs qui ne prennent pas en charge window.onerror, mais présente également certains inconvénients.
Un bloc try/catch ne capturera pas toutes les erreurs d'un programme, telles que les erreurs générées par un bloc de code asynchrone via window.setTimeout
. Try/catch peut être utilisé avec des points d’entrée protégés pour aider à combler les lacunes.
Les blocs try/catch encapsulant l’ensemble de l’application ne suffisent pas pour détecter toutes les erreurs.
Anciennes versions de V8 (et potentiellement d'autres moteurs JS), les fonctions qui contiennent un bloc try/catch ne seront pas optimisées par le compilateur (http://www.html5rocks.com/en/tutorials/speed/v8/). Chrome a résolu ce problème dans TurboFan (https://codereview.chromium.org/1996373002).
Un « point d'entrée » dans JavaScript est toute API de navigateur qui peut démarrer l'exécution de votre code. Les exemples incluent setTimeout
, setInterval
, les écouteurs d'événements, XHR, les sockets Web ou les promesses. Les erreurs générées à partir de ces points d'entrée seront interceptées par window.onerror, mais dans les navigateurs qui ne prennent pas en charge l'objet Error complet dans window.onerror, un mécanisme alternatif est nécessaire pour intercepter ces erreurs puisque la méthode try/catch mentionnée ci-dessus ne les attrapera pas non plus.
Heureusement, JavaScript permet à ces points d'entrée d'être encapsulés afin qu'un bloc try/catch puisse être inséré avant que la fonction ne soit invoquée pour intercepter les erreurs générées par le code.
Chaque point d'entrée aura besoin d'un code légèrement différent pour protéger le point d'entrée, mais l'essentiel de la méthodologie est le suivant :
fonction protégerEntryPoint(fn) { return function protectedFn() {try { return fn();} catch (e) { // Gérer l'erreur.} }}_oldSetTimeout = window.setTimeout;window.setTimeout = function protectedSetTimeout(fn, heure) { return _oldSetTimeout.call(window, protectorEntryPoint(fn), time);};
Malheureusement, il est facile que les erreurs qui se produisent dans les promesses passent inaperçues et ne soient pas signalées. Les erreurs qui se produisent dans une promesse mais qui ne sont pas gérées en attachant un gestionnaire de rejet ne sont signalées nulle part ailleurs - elles ne sont pas signalées à window.onerror
. Même si une promesse attache un gestionnaire de rejet, ce code lui-même doit signaler manuellement ces erreurs pour qu'elles soient enregistrées. Voir http://www.html5rocks.com/en/tutorials/es6/promises/#toc-error-handling pour plus d'informations. Par exemple:
window.onerror = fonction(...) { // Ceci ne sera jamais invoqué par le code Promise.};var p = new Promise(...);p.then(function() { throw new Error("Cette erreur ne sera gérée nulle part.");});var p2 = new Promise(...);p2.then(function() { throw new Error("Cette erreur sera gérée dans la chaîne.");}).catch(function(error) { // Afficher le message d'erreur à l'utilisateur // Ce code doit signaler manuellement l'erreur pour qu'elle soit enregistrée sur le serveur, le cas échéant.});
Une approche pour capturer plus d'informations consiste à utiliser des points d'entrée protégés pour envelopper les appels de méthodes Promise avec un try/catch pour signaler les erreurs. Cela pourrait ressembler à :
var _oldPromiseThen = Promise.prototype.then; Promise.prototype.then = function protectedThen(callback, errorHandler) {return _oldPromiseThen.call(this, protectEntryPoint(callback), protectorEntryPoint(errorHandler)); } ;
Malheureusement, les erreurs de Promises ne seront pas gérées par défaut.
Les implémentations de Promise, telles que Q, Bluebird et Closure, gèrent les erreurs de différentes manières, meilleures que la gestion des erreurs dans l'implémentation de Promises dans le navigateur.
Dans Q, vous pouvez "terminer" la chaîne Promise en appelant .done()
qui garantira que si une erreur n'a pas été gérée dans la chaîne, elle sera renvoyée et signalée. Voir https://github.com/kriskowal/q#handling-errors
Dans Bluebird, les refus non traités sont enregistrés et signalés immédiatement. Voir http://bluebirdjs.com/docs/features.html#surfacing-unhandled-errors
Dans l'implémentation goog.Promise de Closure, les rejets non gérés sont enregistrés et signalés si aucune chaîne dans Promise ne gère le rejet dans un intervalle de temps configurable (afin de permettre au code plus tard dans le programme d'ajouter un gestionnaire de rejet).
La section de trace de pile asynchrone ci-dessus explique que les navigateurs ne capturent pas les informations de pile lorsqu'il existe un hook asynchrone, tel que l'appel de Promise.prototype.then
. Les polyfills Promise offrent un moyen de capturer les points de trace de la pile asynchrone, ce qui peut faciliter le diagnostic des erreurs. Cette approche est coûteuse, mais elle peut être très utile pour capturer davantage d'informations de débogage.
Dans Q, appelez Q.longStackSupport = true;
. Voir https://github.com/kriskowal/q#long-stack-traces
Dans Bluebird, appelez Promise.longStackTraces()
quelque part dans l'application. Voir http://bluebirdjs.com/docs/features.html#long-stack-traces.
Dans Closure, définissez goog.Promise.LONG_STACK_TRACES
sur true.
Chrome 49 a ajouté la prise en charge des événements distribués lorsqu'une promesse est rejetée. Cela permet aux applications de se connecter aux erreurs Promise pour garantir qu'elles soient signalées de manière centralisée avec le reste des erreurs.
window.addEventListener('unhandledrejection', event => { // event.reason contient le motif du rejet. Lorsqu'une erreur est générée, il s'agit de l'objet Error.});
Voir https://googlechrome.github.io/samples/promise-rejection-events/ et https://www.chromestatus.com/feature/4805872211460096 pour plus d'informations.
Ceci n’est pris en charge dans aucun autre navigateur.
Les travailleurs Web, notamment les travailleurs dédiés, les travailleurs partagés et les travailleurs de service, deviennent aujourd'hui de plus en plus populaires dans les applications. Étant donné que tous ces travailleurs sont des scripts distincts de la page principale, ils ont chacun besoin de leur propre code de gestion des erreurs. Il est recommandé que chaque script de travail installe son propre code de gestion des erreurs et de rapport pour une efficacité maximale dans la gestion des erreurs des travailleurs.
Les travailleurs Web dédiés s'exécutent dans un contexte d'exécution différent de celui de la page principale, de sorte que les erreurs des travailleurs ne sont pas détectées par les mécanismes ci-dessus. Des étapes supplémentaires doivent être prises pour capturer les erreurs des travailleurs sur la page.
Lorsqu'un travailleur est créé, la propriété onerror peut être définie sur le nouveau travailleur :
var travailleur = new Worker('worker.js');worker.onerror = function(errorEvent) { ... };
Ceci est défini dans https://html.spec.whatwg.org/multipage/workers.html#handler-abstractworker-onerror. La fonction onerror
sur le travailleur a une signature différente de celle de window.onerror
évoquée ci-dessus. Au lieu d'accepter 5 arguments, worker.onerror
prend un seul argument : un objet ErrorEvent
. L'API de cet objet peut être trouvée sur https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent. Il contient le message, le nom de fichier, la ligne et la colonne, mais aucun navigateur stable ne contient aujourd'hui l'objet "Error" qui contient la trace de la pile (errorEvent.error est nul). Étant donné que cette API est exécutée dans le périmètre de la page parent, elle serait utile pour utiliser le même mécanisme de reporting que la page parent ; malheureusement, en raison de l'absence de trace de pile, cette API est d'une utilité limitée.
À l'intérieur du JS exécuté par le travailleur, vous pouvez également définir une API onerror qui suit l'API window.onerror habituelle : https://html.spec.whatwg.org/multipage/webappapis.html#onerroreventhandler. Dans le code du travailleur :
self.onerror = function(message, nom de fichier, ligne, col, erreur) { ... };
La discussion sur cette API suit principalement la discussion ci-dessus pour window.onerror. Cependant, il y a 2 choses notables à souligner :
Firefox et Safari ne signalent pas l'objet « erreur » comme cinquième argument de la fonction, donc ces navigateurs n'obtiennent pas de trace de pile du travailleur (Chrome, MS Edge et IE11 obtiennent une trace de pile). Les points d'entrée protégés pour la fonction onmessage
au sein du travailleur peuvent être utilisés pour capturer les informations de trace de pile pour ces navigateurs.
Puisque ce code s'exécute au sein du travailleur, le code doit choisir comment signaler l'erreur au serveur : il doit soit utiliser postMessage
pour communiquer l'erreur à la page parent, soit installer un mécanisme de rapport d'erreur XHR (discuté plus en détail ci-dessous) dans le travailleur lui-même.
Dans Firefox, Safari et IE11 (mais pas dans Chrome), la fonction window.onerror
de la page parent sera également appelée après l'appel de la propre onerror du travailleur et de l'écouteur d'événement onerror défini par la page. Cependant, ce window.onerror ne contiendra pas non plus d'objet d'erreur et n'aura donc pas non plus de trace de pile. Ces navigateurs doivent également veiller à ne pas signaler plusieurs fois les erreurs des travailleurs.
Chrome et Firefox prennent en charge l'API SharedWorker pour partager un travailleur sur plusieurs pages. Puisque le travailleur est partagé, il n’est pas attaché exclusivement à une page parent ; cela entraîne certaines différences dans la façon dont les erreurs sont gérées, bien que SharedWorker suive principalement les mêmes informations que le travailleur Web dédié.
Dans Chrome, lorsqu'il y a une erreur dans un SharedWorker, seule la gestion des erreurs du travailleur dans le code du travailleur lui-même sera appelée (comme s'il définissait self.onerror
). Le window.onerror
de la page parent ne sera pas appelé et Chrome ne prend pas en charge le AbstractWorker.onerror
hérité qui peut être appelé dans la page parent comme défini dans la spécification.
Dans Firefox, ce comportement est différent. Une erreur dans le travailleur partagé entraînera l'appel du window.onerror de la page parent, mais l'objet d'erreur sera nul. De plus, Firefox prend en charge la propriété AbstractWorker.onerror
, de sorte que la page parent peut attacher son propre gestionnaire d'erreurs au travailleur. Cependant, lorsque ce gestionnaire d'erreurs est appelé, l'objet d'erreur sera nul, il n'y aura donc aucune trace de pile, son utilité est donc limitée.
La gestion des erreurs pour les nœuds de calcul partagés diffère selon le navigateur.
Les Service Workers sont une toute nouvelle spécification qui n'est actuellement disponible que dans les versions récentes de Chrome et Firefox. Ces travailleurs suivent la même discussion que les travailleurs Web dédiés.
Les service Workers sont installés en appelant la fonction navigator.serviceWorker.register
. Cette fonction renvoie une promesse qui sera rejetée en cas d'erreur lors de l'installation du service worker, par exemple en cas d'erreur lors de l'initialisation. Cette erreur ne contiendra qu'un message de chaîne et rien d'autre. De plus, étant donné que les promesses ne signalent pas les erreurs aux gestionnaires window.onerror
, l'application elle-même devrait ajouter un bloc catch à la promesse pour détecter l'erreur.
navigator.serviceWorker.register('service-worker-installation-error.js').catch(function(error) { // type d'erreur de chaîne});
Tout comme les autres travailleurs, les techniciens de service peuvent définir une fonction self.onerror
au sein des techniciens de service pour détecter les erreurs. Les erreurs d'installation dans le service worker seront signalées à la fonction onerror, mais malheureusement elles ne contiendront pas d'objet d'erreur ni de trace de pile.
L'API Service Worker contient une propriété onerror héritée de l'interface AbstractWorker, mais Chrome ne fait rien