Diese README-Datei enthält Informationen, die ich im Laufe der Jahre über den Umgang mit JavaScript-Fehlern, deren Meldung an den Server und das Navigieren durch viele Fehler gelernt habe, die das Ganze wirklich schwierig machen können. Browser haben sich in diesem Bereich verbessert, aber es gibt noch Raum für Verbesserungen, um sicherzustellen, dass alle Anwendungen alle auftretenden Fehler vernünftig und zuverlässig verarbeiten können.
Testfälle für Inhalte in diesem Handbuch finden Sie unter https://mknichel.github.io/javascript-errors/.
Inhaltsverzeichnis
Einführung
Anatomie eines JavaScript-Fehlers
Es wird ein JavaScript-Fehler erzeugt
Fehlermeldungen
Stack-Trace-Format
JavaScript-Fehler abfangen
window.onerror
versuchen/fangen
Geschützte Einstiegspunkte
Versprechen
Web-Worker
Chrome-Erweiterungen
Das Erkennen, Melden und Beheben von Fehlern ist ein wichtiger Bestandteil jeder Anwendung, um den Zustand und die Stabilität der Anwendung sicherzustellen. Da JavaScript-Code auch auf dem Client und in vielen verschiedenen Browserumgebungen ausgeführt wird, kann es auch schwierig sein, den Überblick über JS-Fehler Ihrer Anwendung zu behalten. Es gibt keine formellen Webspezifikationen für die Meldung von JS-Fehlern, die zu Unterschieden in der Implementierung der einzelnen Browser führen. Darüber hinaus gab es viele Fehler in der Browser-Implementierung von JavaScript-Fehlern, die dies noch schwieriger machten. Auf dieser Seite werden diese Aspekte von JS-Fehlern erläutert, damit zukünftige Entwickler besser mit Fehlern umgehen können und Browser hoffentlich auf standardisierte Lösungen konvergieren.
Ein JavaScript-Fehler besteht aus zwei Hauptbestandteilen: der Fehlermeldung und dem Stack-Trace . Die Fehlermeldung ist eine Zeichenfolge, die beschreibt, was schief gelaufen ist, und der Stack-Trace beschreibt, an welcher Stelle im Code der Fehler aufgetreten ist. JS-Fehler können entweder vom Browser selbst erzeugt oder vom Anwendungscode ausgelöst werden.
Ein JS-Fehler kann vom Browser ausgelöst werden, wenn ein Codeabschnitt nicht ordnungsgemäß ausgeführt wird, oder er kann direkt vom Code ausgelöst werden.
Zum Beispiel:
var a = 3;a();
In diesem Beispiel kann eine Variable, die eigentlich eine Zahl ist, nicht als Funktion aufgerufen werden. Der Browser gibt einen Fehler wie TypeError: a is not a function
mit einem Stack-Trace, der auf diese Codezeile verweist.
Ein Entwickler möchte möglicherweise auch einen Fehler in einem Code auslösen, wenn eine bestimmte Vorbedingung nicht erfüllt ist. Zum Beispiel
if (!checkPrecondition()) { throw new Error("Erfüllt die Vorbedingung nicht!");}
In diesem Fall lautet der Fehler Error: Doesn't meet precondition!
. Dieser Fehler enthält auch einen Stack-Trace, der auf die entsprechende Zeile verweist. Fehler, die vom Browser und vom Anwendungscode ausgelöst werden, können gleich behandelt werden.
Es gibt mehrere Möglichkeiten, wie Entwickler einen Fehler in JavaScript auslösen können:
throw new Error('Problem description.')
throw Error('Problem description.')
<-- entspricht dem ersten
throw 'Problem description.'
<-- schlecht
throw null
<- noch schlimmer
Das Auslösen eines Strings oder einer Null wird wirklich nicht empfohlen, da der Browser diesem Fehler keinen Stack-Trace hinzufügt und so den Kontext verliert, an dem dieser Fehler im Code aufgetreten ist. Am besten werfen Sie ein tatsächliches Error-Objekt aus, das die Fehlermeldung sowie einen Stack-Trace enthält, der auf die richtigen Codezeilen verweist, in denen der Fehler aufgetreten ist.
Jeder Browser verfügt über einen eigenen Satz von Meldungen, die er für die integrierten Ausnahmen verwendet, wie beispielsweise im obigen Beispiel für den Versuch, eine Nichtfunktion aufzurufen. Browser werden versuchen, dieselben Nachrichten zu verwenden. Da es jedoch keine Spezifikation gibt, kann dies nicht garantiert werden. Beispielsweise verwenden sowohl Chrome als auch Firefox für das obige Beispiel {0} is not a function
, während IE11 Function expected
meldet (insbesondere auch ohne Angabe, welche Variable aufgerufen werden soll).
Allerdings weichen auch die Browser häufig voneinander ab. Wenn eine switch
Anweisung mehrere Standardanweisungen enthält, gibt Chrome "More than one default clause in switch statement"
aus, während Firefox "more than one switch default"
meldet. Wenn dem Web neue Funktionen hinzugefügt werden, müssen diese Fehlermeldungen aktualisiert werden. Diese Unterschiede können später zum Tragen kommen, wenn Sie versuchen, gemeldete Fehler aus verschleiertem Code zu behandeln.
Die Vorlagen, die Browser für Fehlermeldungen verwenden, finden Sie unter:
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
Für einige Ausnahmen erzeugen Browser unterschiedliche Fehlermeldungen.
Der Stack-Trace ist eine Beschreibung, wo im Code der Fehler aufgetreten ist. Es besteht aus einer Reihe von Frames, wobei jeder Frame eine bestimmte Zeile im Code beschreibt. Der oberste Frame ist die Stelle, an der der Fehler ausgelöst wurde, während die nachfolgenden Frames den Funktionsaufrufstapel darstellen – oder wie der Code ausgeführt wurde, um zu dem Punkt zu gelangen, an dem der Fehler ausgelöst wurde. Da JavaScript normalerweise verkettet und minimiert wird, ist es auch wichtig, Spaltennummern zu haben, damit die genaue Anweisung gefunden werden kann, wenn eine bestimmte Zeile eine Vielzahl von Anweisungen enthält.
Ein einfacher Stacktrace in Chrome sieht so aus:
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
Jeder Stapelrahmen besteht aus einem Funktionsnamen (sofern zutreffend und der Code wurde nicht im globalen Bereich ausgeführt), dem Skript, aus dem er stammt, sowie der Zeilen- und Spaltennummer des Codes.
Leider gibt es keinen Standard für das Stack-Trace-Format, sodass dieses je nach Browser unterschiedlich ist.
Der Stack-Trace von Microsoft Edge und IE 11 ähnelt dem von Chrome, außer dass darin explizit globaler Code aufgeführt ist:
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)
Der Stacktrace von Firefox sieht folgendermaßen aus:
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
Das Format von Safari ähnelt dem Format von Firefox, unterscheidet sich jedoch geringfügig:
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
Es sind die gleichen grundlegenden Informationen vorhanden, aber das Format ist unterschiedlich.
Beachten Sie außerdem, dass sich im Safari-Beispiel nicht nur das Format von Chrome, sondern auch die Spaltennummern von Chrome und Firefox unterscheiden. Die Spaltennummern können in verschiedenen Fehlersituationen auch stärker abweichen – zum Beispiel im Code (function namedFunction() { throwError(); })();
, meldet Chrome die Spalte für den Funktionsaufruf throwError()
, während IE11 die Spaltennummer als Anfang der Zeichenfolge meldet. Diese Unterschiede werden später wieder zum Tragen kommen, wenn der Server den Stack-Trace auf gemeldete Fehler analysieren und verschleierte Stack-Traces entschlüsseln muss.
Weitere Informationen zur Stack-Eigenschaft von Fehlern finden Sie unter https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack. Beim Zugriff auf die Eigenschaft „Error.stack“ fügt Chrome die Fehlermeldung zwar in den Stapel ein, Safari 10+ jedoch nicht.
Das Format von Stacktraces unterscheidet sich je nach Browser in Form und verwendeten Spaltennummern.
Wenn wir näher darauf eingehen, gibt es viele Nuancen bei Stack-Trace-Formaten, die in den folgenden Abschnitten besprochen werden.
Standardmäßig haben anonyme Funktionen keinen Namen und erscheinen entweder als leere Zeichenfolge oder als „Anonyme Funktion“ in den Funktionsnamen im Stacktrace (je nach Browser). Um das Debuggen zu verbessern, sollten Sie allen Funktionen einen Namen hinzufügen, um sicherzustellen, dass sie im Stapelrahmen angezeigt werden. Der einfachste Weg, dies zu erreichen, besteht darin, sicherzustellen, dass anonyme Funktionen mit einem Namen angegeben werden, auch wenn dieser Name nirgendwo anders verwendet wird. Zum Beispiel:
setTimeout(function nameOfTheAnonymousFunction() { ... }, 0);
Dies führt dazu, dass der Stack-Trace von Folgendem ausgeht:
at http://mknichel.github.io/javascript-errors/javascript-errors.js:125:17
Zu
at nameOfTheAnonymousFunction (http://mknichel.github.io/javascript-errors/javascript-errors.js:121:31)
In Safari würde dies wie folgt aussehen:
https://mknichel.github.io/javascript-errors/javascript-errors.js:175:27
Zu
nameOfTheAnonymousFunction@https://mknichel.github.io/javascript-errors/javascript-errors.js:171:41
Diese Methode stellt sicher, dass nameOfTheAnonymousFunction
im Frame für jeden Code innerhalb dieser Funktion angezeigt wird, was das Debuggen erheblich erleichtert. Weitere Informationen finden Sie unter http://www.html5rocks.com/en/tutorials/developertools/async-call-stack/#toc-debugging-tips.
Browser verwenden auch den Namen der Variablen oder Eigenschaft, der eine Funktion zugewiesen ist, wenn die Funktion selbst keinen Namen hat. Zum Beispiel in
var fnVariableName = function() { ... };
Browser verwenden fnVariableName
als Namen der Funktion in Stacktraces.
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)
Noch nuancierter: Wenn diese Variable in einer anderen Funktion definiert ist, verwenden alle Browser nur den Namen der Variablen als Namen der Funktion im Stacktrace, mit Ausnahme von Firefox, der eine andere Form verwendet, die den Namen von verkettet die äußere Funktion mit dem Namen der inneren Variablen. Beispiel:
Funktion throwErrorFromInnerFunctionAssignedToVariable() { var fnVariableName = function() { throw new Error("foo"); }; fnVariableName();}
erzeugt in Firefox:
throwErrorFromInnerFunctionAssignedToVariable/fnVariableName@http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37
In anderen Browsern würde dies so aussehen:
at fnVariableName (http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37)
Firefox verwendet unterschiedliche Stapelrahmentexte für Funktionen, die innerhalb einer anderen Funktion definiert sind.
Der Anzeigename einer Funktion kann in allen gängigen Browsern außer IE11 auch über die Eigenschaft displayName
festgelegt werden. In diesen Browsern wird der displayName im Devtools-Debugger angezeigt, aber in allen Browsern außer Safari wird er nicht in Fehler-Stack-Traces verwendet (Safari unterscheidet sich von den anderen dadurch, dass der displayName auch im mit einem Fehler verbundenen Stack-Trace verwendet wird).
var someFunction = function() {};someFunction.displayName = " # Eine längere Beschreibung der Funktion.";
Es gibt keine offizielle Spezifikation für die displayName-Eigenschaft, sie wird jedoch von allen gängigen Browsern unterstützt. Siehe https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/displayName und http://www.alertdebugging.com/2009/04/29/building-a-better -javascript-profiler-with-webkit/ für weitere Informationen zu displayName.
IE11 unterstützt die displayName-Eigenschaft nicht.
Safari verwendet die displayName-Eigenschaft als Symbolnamen in Fehler-Stack-Traces.
Wenn ein Fehler ohne Stack-Trace gemeldet wird (weitere Einzelheiten dazu finden Sie weiter unten), ist es möglich, einen Stack-Trace programmgesteuert zu erfassen.
In Chrome ist dies mithilfe der Error.captureStackTrace
API ganz einfach möglich. Weitere Informationen zur Verwendung dieser API finden Sie unter https://github.com/v8/v8/wiki/Stack%20Trace%20API.
Zum Beispiel:
FunktionignoreThisFunctionInStackTrace() { var err = new Error(); Error.captureStackTrace(err,ignoreThisFunctionInStackTrace); return err.stack;}
In anderen Browsern kann ein Stack-Trace auch erfasst werden, indem ein neuer Fehler erstellt und auf die Stack-Eigenschaft dieses Objekts zugegriffen wird:
var err = new Error('');return err.stack;
Allerdings füllt IE10 den Stack-Trace nur dann auf, wenn der Fehler tatsächlich ausgelöst wird:
versuchen { throw new Error('');} Catch (e) { e.stack zurückgeben;}
Wenn keiner dieser Ansätze funktioniert, ist es möglich, einen groben Stack-Trace ohne Zeilennummern oder Spalten zu erstellen, indem Sie über das arguments.callee.caller
-Objekt iterieren. Dies funktioniert jedoch im ES5 Strict Mode nicht und ist kein empfohlener Ansatz.
Es kommt sehr häufig vor, dass asynchrone Punkte in JavaScript-Code eingefügt werden, beispielsweise wenn der Code setTimeout
verwendet oder Promises verwendet. Diese asynchronen Einstiegspunkte können Probleme für Stack-Traces verursachen, da sie dazu führen, dass ein neuer Ausführungskontext entsteht und der Stack-Trace von vorne beginnt.
Chrome DevTools unterstützt asynchrone Stack-Traces, d. h. stellt sicher, dass im Stack-Trace eines Fehlers auch die Frames angezeigt werden, die vor der Einführung des Async-Punkts aufgetreten sind. Durch die Verwendung von setTimeout wird erfasst, wer die setTimeout-Funktion aufgerufen hat, die letztendlich einen Fehler verursacht hat. Weitere Informationen finden Sie unter http://www.html5rocks.com/en/tutorials/developertools/async-call-stack/.
Ein asynchroner Stack-Trace sieht folgendermaßen aus:
throwError @ throw-error.js:2 setTimeout (async) throwErrorAsync @ throw-error.js:10 (anonymous function) @ throw-error-basic.html:14
Asynchrone Stack-Traces werden derzeit nur in Chrome DevTools unterstützt, und zwar nur für Ausnahmen, die ausgelöst werden, wenn DevTools geöffnet sind. Stack-Traces, auf die über Fehlerobjekte im Code zugegriffen wird, enthalten nicht den asynchronen Stack-Trace.
In einigen Fällen ist es möglich, asynchrone Stack-Traces mehrfach zu füllen. Dies könnte jedoch zu erheblichen Leistungseinbußen für Ihre Anwendung führen, da die Erfassung eines Stack-Traces nicht billig ist.
Nur Chrome DevTools unterstützt nativ asynchrone Stack-Traces.
Stacktraces für Code, der ausgewertet oder in eine HTML-Seite eingebunden wurde, verwenden die URL der Seite und die Zeilen-/Spaltennummern für den ausgeführten Code.
Zum Beispiel:
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
Wenn diese Skripte tatsächlich von einem Skript stammen, das aus Optimierungsgründen inline eingefügt wurde, sind URL, Zeilen- und Spaltennummern falsch. Um dieses Problem zu umgehen, unterstützen Chrome und Firefox die Annotation //# sourceURL=
(Safari, Edge und IE nicht). Die in dieser Annotation angegebene URL wird als URL für alle Stack-Traces verwendet und die Zeilen- und Spaltennummer wird relativ zum Anfang des -Tags und nicht zum HTML-Dokument berechnet. Für den gleichen Fehler wie oben führt die Verwendung der sourceURL-Annotation mit dem Wert „inline.js“ zu einem Stack-Trace, der wie folgt aussieht:
at throwError (http://mknichel.github.io/javascript-errors/inline.js:8:9) at http://mknichel.github.io/javascript-errors/inline.js:12:3
Dies ist eine wirklich praktische Technik, um sicherzustellen, dass Stack-Traces auch dann noch korrekt sind, wenn Inline-Skripte und Auswertungen verwendet werden.
http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl beschreibt die sourceURL-Annotation ausführlicher.
Safari, Edge und IE unterstützen die SourceURL-Annotation zum Benennen von Inline-Skripten und Auswertungen nicht. Wenn Sie Inline-Skripte in IE oder Safari verwenden und Ihren Code verschleiern, können Sie Fehler, die von diesen Skripten herrühren, nicht entschleieren.
Bis Chrome 42 hat Chrome die Zeilennummern für Inline-Skripte, die die Annotation „sourceURL“ verwenden, nicht korrekt berechnet. Weitere Informationen finden Sie unter https://bugs.chromium.org/p/v8/issues/detail?id=3920.
Zeilennummern für Stapelrahmen aus Inline-Skripten sind falsch, wenn die Annotation „sourceURL“ verwendet wird, da sie relativ zum Anfang des HTML-Dokuments und nicht zum Anfang des Inline-Skript-Tags sind (wodurch eine korrekte Entschleierung nicht möglich ist). https://code.google.com/p/chromium/issues/detail?id=578269
Für Code, der eval verwendet, gibt es neben der Frage, ob die Annotation „sourceURL“ verwendet wird, weitere Unterschiede im Stack-Trace. In Chrome könnte ein Stack-Trace einer in eval verwendeten Anweisung wie folgt aussehen:
Error: Error from eval at evaledFunction (eval at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3),:1:36) at eval (eval at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3), :1:68) at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3)
In MS Edge und IE11 würde dies so aussehen:
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)
In Safari:
Error from eval evaledFunction eval code eval@[native code] evalError@http://mknichel.github.io/javascript-errors/javascript-errors.js:137:7
und in 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
Diese Unterschiede können es schwierig machen, den Evaluierungscode in allen Browsern gleich zu analysieren.
Jeder Browser verwendet ein anderes Stack-Trace-Format für Fehler, die innerhalb von eval aufgetreten sind.
Ihr JavaScript-Code kann auch direkt aus nativem Code aufgerufen werden. Array.prototype.forEach
ist ein gutes Beispiel – Sie übergeben eine Funktion an forEach
und die JS-Engine ruft diese Funktion für Sie auf.
Funktion throwErrorWithNativeFrame() { var arr = [0, 1, 2, 3]; arr.forEach(function benanntFn(value) {throwError(); });}
Dies führt in verschiedenen Browsern zu unterschiedlichen Stack-Traces. Chrome und Safari hängen den Namen der nativen Funktion im Stacktrace selbst als separaten Frame an, z. B.:
(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)
Allerdings zeigen Firefox und IE11 nicht an, dass forEach
als Teil des Stacks aufgerufen wurde:
namedFn@http://mknichel.github.io/javascript-errors/javascript-errors.js:153:5 throwErrorWithNativeFrame@http://mknichel.github.io/javascript-errors/javascript-errors.js:152:3
Einige Browser integrieren native Code-Frames in Stack-Traces, andere nicht.
Um zu erkennen, dass in Ihrer Anwendung ein Fehler aufgetreten ist, muss ein Code in der Lage sein, diesen Fehler abzufangen und darüber zu berichten. Es gibt mehrere Techniken zum Erkennen von Fehlern, jede mit ihren Vor- und Nachteilen.
window.onerror
ist eine der einfachsten und besten Möglichkeiten, mit der Fehlererkennung zu beginnen. Durch die Zuweisung von window.onerror
zu einer Funktion wird jeder Fehler, der von einem anderen Teil der Anwendung nicht erkannt wird, zusammen mit einigen Informationen über den Fehler an diese Funktion gemeldet. Zum Beispiel:
window.onerror = function(msg, url, line, col, err) { console.log('Bei der Anwendung ist ein Fehler aufgetreten: ' + msg); console.log('Stack Trace: ' + err.stack);}
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror beschreibt dies ausführlicher.
In der Vergangenheit gab es bei diesem Ansatz einige Probleme:
Kein Fehlerobjekt bereitgestellt
Das fünfte Argument der Funktion window.onerror
soll ein Error-Objekt sein. Dies wurde 2013 zur WHATWG-Spezifikation hinzugefügt: https://html.spec.whatwg.org/multipage/webappapis.html#errorevent. Chrome, Firefox und IE11 stellen jetzt ordnungsgemäß ein Fehlerobjekt bereit (zusammen mit der kritischen Stapeleigenschaft), Safari, MS Edge und IE10 jedoch nicht. Dies funktioniert in Firefox seit Firefox 14 (https://bugzilla.mozilla.org/show_bug.cgi?id=355430) und in Chrome seit Ende 2013 (https://mikewest.org/2013/08/debugging-runtime-errors). -with-window-onerror, https://code.google.com/p/chromium/issues/detail?id=147127). Safari 10 hat die Unterstützung für das Error-Objekt in window.onerror eingeführt.
Safari (Versionen unter 10), MS Edge und IE10 unterstützen kein Fehlerobjekt mit einem Stack-Trace in window.onerror.
Domainübergreifende Bereinigung
In Chrome werden Fehler, die von einer anderen Domäne stammen, im window.onerror-Handler auf „Skriptfehler.“, „“, 0 bereinigt. Dies ist im Allgemeinen in Ordnung, wenn Sie den Fehler wirklich nicht verarbeiten möchten, wenn er von einer kommt Skript, das Sie nicht interessiert, damit die Anwendung Fehler herausfiltern kann, die wie folgt aussehen. Dies geschieht jedoch nicht in Firefox, Safari oder IE11, und Chrome tut dies auch nicht für Try/Catch-Blöcke, die den fehlerhaften Code umschließen.
Wenn Sie Fehler in window.onerror
in Chrome mit voller Wiedergabetreue von domänenübergreifenden Skripten erhalten möchten, müssen diese Ressourcen die entsprechenden Cross-Origin-Header bereitstellen. Weitere Informationen finden Sie unter https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror.
Chrome ist der einzige Browser, der Fehler bereinigt, die von einem anderen Ursprung stammen. Achten Sie darauf, diese herauszufiltern, oder setzen Sie die entsprechenden Header.
Chrome-Erweiterungen
In alten Versionen von Chrome können Chrome-Erweiterungen, die auf dem Computer eines Benutzers installiert sind, ebenfalls Fehler auslösen, die an window.onerror gemeldet werden. Dies wurde in neueren Versionen von Chrome behoben. Weitere Informationen finden Sie weiter unten im Abschnitt zu den Chrome-Erweiterungen.
Die window.addEventListener("error")
API funktioniert genauso wie die window.onerror-API. Weitere Informationen zu diesem Ansatz finden Sie unter http://www.w3.org/html/wg/drafts/html/master/webappapis.html#runtime-script-errors.
Das Abfangen von Fehlern über window.onerror verhindert nicht, dass dieser Fehler auch in der DevTools-Konsole angezeigt wird. Dies ist höchstwahrscheinlich das richtige Verhalten für die Entwicklung, da der Entwickler den Fehler leicht erkennen kann. Wenn Sie nicht möchten, dass diese Fehler in der Produktion den Endbenutzern angezeigt werden, kann e.preventDefault()
aufgerufen werden, wenn der window.addEventListener-Ansatz verwendet wird.
window.onerror ist das beste Tool, um JS-Fehler zu erkennen und zu melden. Es wird empfohlen, dass nur JS-Fehler mit gültigen Fehlerobjekten und Stack-Traces an den Server zurückgemeldet werden, da die Fehler sonst möglicherweise schwer zu untersuchen sind oder Sie möglicherweise viel Spam von Chrome-Erweiterungen oder domänenübergreifenden Skripten erhalten.
Angesichts des obigen Abschnitts ist es leider nicht möglich, sich in allen Browsern auf window.onerror
zu verlassen, um alle Fehlerinformationen zu erfassen. Um Ausnahmen lokal abzufangen, ist ein Try/Catch-Block die offensichtliche Wahl. Es ist auch möglich, ganze JavaScript-Dateien in einen Try/Catch-Block zu packen, um Fehlerinformationen zu erfassen, die mit window.onerror nicht abgefangen werden können. Dies verbessert die Situation für Browser, die window.onerror nicht unterstützen, hat aber auch einige Nachteile.
Ein Try/Catch-Block erfasst nicht alle Fehler in einem Programm, beispielsweise Fehler, die von einem asynchronen Codeblock über window.setTimeout
ausgelöst werden. Try/Catch kann mit geschützten Einstiegspunkten verwendet werden, um die Lücken zu schließen.
Try/Catch-Blöcke, die die gesamte Anwendung umschließen, reichen nicht aus, um alle Fehler abzufangen.
In alten Versionen von V8 (und möglicherweise anderen JS-Engines) werden Funktionen, die einen Try/Catch-Block enthalten, vom Compiler nicht optimiert (http://www.html5rocks.com/en/tutorials/speed/v8/). Chrome hat dies in TurboFan behoben (https://codereview.chromium.org/1996373002).
Ein „Einstiegspunkt“ in JavaScript ist jede Browser-API, die die Ausführung Ihres Codes starten kann. Beispiele hierfür sind setTimeout
, setInterval
, Ereignis-Listener, XHR, Web-Sockets oder Versprechen. Fehler, die von diesen Einstiegspunkten ausgelöst werden, werden von window.onerror abgefangen, aber in den Browsern, die nicht das vollständige Error-Objekt in window.onerror unterstützen, ist neben der erwähnten try/catch-Methode ein alternativer Mechanismus zum Abfangen dieser Fehler erforderlich oben wird sie auch nicht fangen.
Glücklicherweise ermöglicht JavaScript das Umschließen dieser Einstiegspunkte, sodass ein Try/Catch-Block eingefügt werden kann, bevor die Funktion aufgerufen wird, um alle vom Code ausgelösten Fehler abzufangen.
Jeder Einstiegspunkt benötigt einen etwas anderen Code, um den Einstiegspunkt zu schützen, aber der Kern der Methodik ist:
Funktion ProtectEntryPoint(fn) { return function protectedFn() {try { return fn();} Catch (e) { // Fehler behandeln.} }}_oldSetTimeout = window.setTimeout;window.setTimeout = function protectedSetTimeout(fn, time) { return _oldSetTimeout.call(window, protectedEntryPoint(fn), time);};
Leider kann es leicht passieren, dass Fehler, die in Promises passieren, unbemerkt bleiben und nicht gemeldet werden. Fehler, die in einem Promise auftreten, aber nicht durch Anhängen eines Ablehnungshandlers behandelt werden, werden nirgendwo anders gemeldet – sie werden nicht an window.onerror
gemeldet. Selbst wenn ein Promise einen Ablehnungshandler anfügt, muss dieser Code selbst diese Fehler manuell melden, damit sie protokolliert werden. Weitere Informationen finden Sie unter http://www.html5rocks.com/en/tutorials/es6/promises/#toc-error-handling. Zum Beispiel:
window.onerror = function(...) { // Dies wird niemals durch Promise-Code aufgerufen.};var p = new Promise(...);p.then(function() { throw new Error("Dieser Fehler wird nirgendwo behandelt.");});var p2 = new Promise(...);p2.then(function() { throw new Error("Dieser Fehler wird in der Kette behandelt.");}).catch(function(error) { // Fehlermeldung dem Benutzer anzeigen // Dieser Code sollte den Fehler manuell melden, damit er ggf. auf dem Server protokolliert wird.});
Ein Ansatz zum Erfassen weiterer Informationen besteht darin, geschützte Einstiegspunkte zu verwenden, um Aufrufe von Promise-Methoden mit einem Try/Catch zu verpacken, um Fehler zu melden. Das könnte so aussehen:
var _oldPromiseThen = Promise.prototype.then; Promise.prototype.then = function protectedThen(callback, errorHandler) {return _oldPromiseThen.call(this, ProtectEntryPoint(Callback), ProtectEntryPoint(errorHandler)); };
Leider werden Fehler von Promises standardmäßig nicht behandelt.
Promise-Implementierungen wie Q, Bluebird und Closure behandeln Fehler auf unterschiedliche Weise, was besser ist als die Fehlerbehandlung in der Browser-Implementierung von Promises.
In Q können Sie die Promise-Kette „beenden“, indem Sie .done()
aufrufen. Dadurch wird sichergestellt, dass ein Fehler, der in der Kette nicht behandelt wurde, erneut ausgelöst und gemeldet wird. Siehe https://github.com/kriskowal/q#handling-errors
In Bluebird werden nicht bearbeitete Ablehnungen protokolliert und sofort gemeldet. Siehe http://bluebirdjs.com/docs/features.html#surfacing-unhandled-errors
In der goog.Promise-Implementierung von Closure werden nicht behandelte Ablehnungen protokolliert und gemeldet, wenn keine Kette im Promise die Ablehnung innerhalb eines konfigurierbaren Zeitintervalls verarbeitet (damit Code später im Programm einen Ablehnungshandler hinzufügen kann).
Im Abschnitt zur asynchronen Stapelverfolgung oben wird erläutert, dass Browser keine Stapelinformationen erfassen, wenn ein asynchroner Hook vorhanden ist, z. B. beim Aufruf Promise.prototype.then
. Promise-Polyfills bieten eine Möglichkeit, die asynchronen Stack-Trace-Punkte zu erfassen, was die Fehlerdiagnose erheblich erleichtern kann. Dieser Ansatz ist teuer, kann aber für die Erfassung weiterer Debug-Informationen sehr nützlich sein.
Rufen Sie in Q Q.longStackSupport = true;
. Siehe https://github.com/kriskowal/q#long-stack-traces
Rufen Sie in Bluebird irgendwo in der Anwendung Promise.longStackTraces()
auf. Siehe http://bluebirdjs.com/docs/features.html#long-stack-traces.
Setzen Sie in „Closure“ goog.Promise.LONG_STACK_TRACES
auf „true“.
Chrome 49 hat Unterstützung für Ereignisse hinzugefügt, die ausgelöst werden, wenn ein Versprechen abgelehnt wird. Dies ermöglicht es Anwendungen, sich in Promise-Fehler einzuklinken, um sicherzustellen, dass sie zusammen mit den übrigen Fehlern zentral gemeldet werden.
window.addEventListener('unhandledrejection', event => { // event.reason enthält den Ablehnungsgrund. Wenn ein Fehler ausgelöst wird, ist dies das Error-Objekt.});
Weitere Informationen finden Sie unter https://googlechrome.github.io/samples/promise-rejection-events/ und https://www.chromestatus.com/feature/4805872211460096.
Dies wird in keinem anderen Browser unterstützt.
Web-Worker, darunter dedizierte Worker, Shared Worker und Service-Worker, werden heutzutage in Anwendungen immer beliebter. Da es sich bei all diesen Workern um separate Skripte der Hauptseite handelt, benötigen sie jeweils ihren eigenen Fehlerbehandlungscode. Es wird empfohlen, dass jedes Worker-Skript seinen eigenen Fehlerbehandlungs- und Berichtscode installiert, um die Fehler von Workern maximal effektiv zu behandeln.
Dedizierte Web-Worker werden in einem anderen Ausführungskontext als die Hauptseite ausgeführt, sodass Fehler von Workern nicht von den oben genannten Mechanismen abgefangen werden. Es müssen zusätzliche Schritte unternommen werden, um Fehler von Arbeitern auf der Seite zu erfassen.
Wenn ein Worker erstellt wird, kann die Eigenschaft onerror für den neuen Worker festgelegt werden:
var worker = new Worker('worker.js');worker.onerror = function(errorEvent) { ... };
Dies ist in https://html.spec.whatwg.org/multipage/workers.html#handler-abstractworker-onerror definiert. Die onerror
-Funktion auf dem Worker hat eine andere Signatur als die oben besprochene window.onerror
. Anstatt fünf Argumente zu akzeptieren, akzeptiert worker.onerror
ein einzelnes Argument: ein ErrorEvent
Objekt. Die API für dieses Objekt finden Sie unter https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent. Es enthält die Nachricht, den Dateinamen, die Zeile und die Spalte, aber kein stabiler Browser enthält heutzutage das „Error“-Objekt, das den Stack-Trace enthält (errorEvent.error ist null). Da diese API im Bereich der übergeordneten Seite ausgeführt wird, wäre sie nützlich, um denselben Berichtsmechanismus wie die übergeordnete Seite zu verwenden. Leider ist diese API aufgrund des Fehlens eines Stack-Trace von begrenztem Nutzen.
Innerhalb des vom Worker ausgeführten JS können Sie auch eine onerror-API definieren, die der üblichen window.onerror-API folgt: https://html.spec.whatwg.org/multipage/webappapis.html#onerroreventhandler. Im Worker-Code:
self.onerror = function(message, filename, line, col, error) { ... };
Die Diskussion dieser API folgt größtenteils der obigen Diskussion für window.onerror. Es gibt jedoch zwei bemerkenswerte Dinge, auf die hingewiesen werden muss:
Firefox und Safari melden das „error“-Objekt nicht als fünftes Argument an die Funktion, daher erhalten diese Browser keinen Stack-Trace vom Worker (Chrome, MS Edge und IE11 erhalten einen Stack-Trace). Geschützte Einstiegspunkte für die onmessage
-Funktion innerhalb des Workers können verwendet werden, um Stack-Trace-Informationen für diese Browser zu erfassen.
Da dieser Code innerhalb des Workers ausgeführt wird, muss der Code auswählen, wie der Fehler an den Server zurückgemeldet werden soll: Er muss entweder postMessage
verwenden, um den Fehler an die übergeordnete Seite zurückzumelden, oder einen XHR-Fehlermeldemechanismus installieren (mehr dazu weiter unten). der Arbeitnehmer selbst.
In Firefox, Safari und IE11 (jedoch nicht in Chrome) wird die window.onerror
Funktion der übergeordneten Seite auch aufgerufen, nachdem der eigene onerror des Workers und der von der Seite festgelegte onerror-Ereignis-Listener aufgerufen wurden. Dieser window.onerror enthält jedoch auch kein Fehlerobjekt und verfügt daher auch nicht über einen Stack-Trace. Diese Browser müssen außerdem darauf achten, Fehler von Mitarbeitern nicht mehrfach zu melden.
Chrome und Firefox unterstützen die SharedWorker-API für die gemeinsame Nutzung eines Workers auf mehreren Seiten. Da der Worker geteilt wird, ist er nicht ausschließlich einer übergeordneten Seite zugeordnet; Dies führt zu einigen Unterschieden in der Fehlerbehandlung, obwohl SharedWorker größtenteils denselben Informationen folgt wie der dedizierte Web-Worker.
Wenn in Chrome ein Fehler in einem SharedWorker auftritt, wird nur die eigene Fehlerbehandlung des Workers im Worker-Code selbst aufgerufen (z. B. wenn self.onerror
festgelegt wird). Die window.onerror
der übergeordneten Seite wird nicht aufgerufen und Chrome unterstützt nicht die geerbte AbstractWorker.onerror
, die gemäß der Definition in der Spezifikation auf der übergeordneten Seite aufgerufen werden kann.
In Firefox ist dieses Verhalten anders. Ein Fehler im Shared Worker führt dazu, dass window.onerror der übergeordneten Seite aufgerufen wird, das Fehlerobjekt jedoch null ist. Darüber hinaus unterstützt Firefox die Eigenschaft AbstractWorker.onerror
, sodass die übergeordnete Seite dem Worker einen eigenen Fehlerhandler hinzufügen kann. Wenn dieser Fehlerhandler jedoch aufgerufen wird, ist das Fehlerobjekt null, sodass es keinen Stack-Trace gibt und daher nur von begrenztem Nutzen ist.
Die Fehlerbehandlung für gemeinsam genutzte Worker unterscheidet sich je nach Browser.
Service Worker sind eine brandneue Spezifikation, die derzeit nur in neueren Chrome- und Firefox-Versionen verfügbar ist. Diese Worker verfolgen die gleiche Diskussion wie engagierte Web-Worker.
Service Worker werden durch Aufrufen der Funktion navigator.serviceWorker.register
installiert. Diese Funktion gibt ein Versprechen zurück, das abgelehnt wird, wenn bei der Installation des Service-Workers ein Fehler aufgetreten ist, beispielsweise wenn während der Initialisierung ein Fehler ausgegeben wurde. Dieser Fehler enthält nur eine Zeichenfolgenmeldung und sonst nichts. Da Promises außerdem keine Fehler an window.onerror
-Handler melden, müsste die Anwendung selbst dem Promise einen Catch-Block hinzufügen, um den Fehler abzufangen.
navigator.serviceWorker.register('service-worker-installation-error.js').catch(function(error) { // Fehlertyp der Zeichenfolge});
Genau wie die anderen Mitarbeiter können Servicemitarbeiter innerhalb der Servicemitarbeiter eine self.onerror
-Funktion einrichten, um Fehler zu erkennen. Installationsfehler im Service Worker werden an die Funktion onerror gemeldet, enthalten aber leider kein Fehlerobjekt oder Stacktrace.
Die Service-Worker-API enthält eine von der AbstractWorker-Schnittstelle geerbte onerror-Eigenschaft, Chrome führt jedoch keine Aktionen aus