Benutzer:Rolf b/Fehlerbehandung
Inhaltsverzeichnis
Fehlerbehandlung mit try..catch
Die Fehlerbehandlung mit dem error
-Eventhandler ist etwas, das außerhalb von JavaScript stattfindet und Teil der JavaScript-Integration in den Browser ist. Der größte Nachteil des error
-Eventhandlers ist, dass das fehlerhafte Script bereits abgebrochen wurde, wenn der Browser ihn aufruft. Sie haben damit keine Chance mehr, eine konkrete Fehlerbehandlung zu programmieren und das Script möglicherweise sinnvoll fortzusetzen.
JavaScript bringt aber auch einen Satz an Bordmitteln zur Fehlerbehandlung mit:
- das
try..catch
Konstrukt zum Erstellen eines „Fangnetzes“ für Fehler - die Anweisung
throw
, um eigene Fehler auszulösen - das Error-Objekt, um Fehler standardisiert darzustellen
JavaScript wirft an vielen Stellen Error-Objekte, um eine Fehlersituation zu beschreiben. Eins davon ist zum Beispiel der ReferenceError
, wenn Sie auf eine Variable zugreifen, die im aktuellen Scope nicht deklariert ist. Sie könnten den ReferenceError beispielsweise nutzen, wenn Sie ein Script schreiben, das eine bestimmte globale Variablen nutzen soll, sofern sie vorhanden ist (das ist nicht bester Programmierstil, aber manchmal geht es nicht anders). Die Abfrage, ob das window-Objekt über eine Eigenschaft dieses Namens verfügt, kann ein try..catch nicht ersetzen, weil let- und const-Deklarationen keine Eigenschaft im window-Objekt erzeugen.
function fragHugo() {
try {
return hugo;
}
catch (fehler) {
if (fehler instanceof ReferenceError) {
console.log("Wo ist Hugo?", fehler.message);
return 42;
}
else {
console.log("Die Suche nach Hugo ist gescheitert:", fehler.message);
throw fehler;
}
}
}
console.log("Versuch 1: hugo ist %d", fragHugo());
const hugo = 99;
console.log("Versuch 2: hugo ist %d", fragHugo());
setTimeout("x=3", 200);
function zeigeErgebnis (Zaehler, Ergebnis) {
alert("Nach " + (Zaehler) + " Durchläufen existierte x.\nDie Zahl x ist " + Ergebnis + ".")
}
function teste_x (Zaehler) {
try {
if (x == 2) {
throw "richtig";
} else if (x == 3) {
throw "falsch";
}
} catch (e) {
if (e == "richtig") {
zeigeErgebnis(Zaehler, e);
return;
} else if (e == "falsch") {
zeigeErgebnis(Zaehler, e);
return;
}
} finally {
Zaehler++;
}
setTimeout("teste_x(" + Zaehler + ")", 30);
}
teste_x(0);
Das Beispiel besteht aus der Funktion fragHugo
, die die Variable hugo
erwartet, darauf zugreifen möchte und eine Alternative braucht, wenn die Variable fehlt. Hinter der Funktion finden Sie zwei Testaufrufe sowie die Deklaration der Variablen hugo
, leider erst nach dem ersten Aufruf.
Die Variable hugo
Die Variable hugo
wird zwar mittels const deklariert und ist deswegen als Name im Script bekannt, sie wird aber erst nach dem ersten Aufruf von fragHugo
initialisiert. Der erste Aufruf von fragHugo
befindet sich deshalb in der temporal dead zone der Deklaration, wo hugo
noch nicht verwendet werden darf.
Die Funktion fragHugo()
In dieser Funktion soll der Wert der Variablen hugo
ausgelesen und verwendet werden. Für unser Beispiel wird der Wert einfach nur zurückgegeben.
Wenn auf hugo
nicht zugegriffen werden kann, weil die Variable nicht deklariert wurde oder sie sich, wie hier, in der temporären toten Zone befindet, wirft JavaScript beim Zugriffsversuch einen ReferenceError
. Deshalb verwendet die Funktion das try..catch
-Statement, um auf Fehler beim Zugriffsversuch reagieren zu können.
Aufbau des try..catch-Statements
Das try..catch
-Statement beginnt mit einem Anweisungsblock, in dem ausgelöste Fehler abgefangen werden sollen. Dazu notieren Sie das Schlüsselwort try
(try = versuchen) und die zu überwachenden Anweisungen, die einen Anweisungsblock in geschweiften Klammern bilden müssen. Innerhalb dieses Anweisungsblocks werden alle Fehler abgefangen, die darin direkt geworfen werden, von Funktionen, die Sie dort aufrufen oder von der JavaScript-Laufzeitumgebung. Zum Werfen von Fehlern dient die Anweisung throw
, auf die später genauer eingegangen wird.
Auf den try
-Block folgt der catch
-Block (catch = fangen). Er besteht aus dem Schlüsselwort catch
, wonach Sie ähnlich wie bei einer JavaScript/Funktion einen Parameter notieren können, in dem Sie das geworfene Error-Objekt entgegennehmen können. Der Parameter ist optional, aber Sie brauchen ihn, wenn Sie den geworfenen Fehler protokollieren oder analysieren wollen. Daran anschließend folgt ein Anweisungsblock mit den Anweisungen, die im Falle eines Fehlers auszuführen sind.
Als drittes kann noch ein finally
-Block stehen (finally = zum Abschluss). Er besteht aus dem Schlüsselwort finally
, gefolgt von einem Anweisungsblock, der zum Abschluss des try
-Konstrukts immer auszuführen ist, ganz gleich, ob ein Fehler auftrat oder nicht.
Sowohl catch
- wie auch finally
-Block sind optional, dürfen aber nicht beide fehlen. Ein allein stehender try
-Block ist sinnlos und führt zu einem SyntaxError. Ein try..finally
-Konstrukt, also ohne ein catch
, ist dann sinnvoll, wenn Sie einen geworfenen Fehler nicht fangen möchten (und sich darauf verlassen, dass weiter oben in der Aufrufhierarchie ein catch
bereitsteht), aber Code haben, der immer ausgeführt werden muss, ganz gleich, ob ein Fehler geworfen wurde oder nicht. Solche finally-Blöcke dienen typischerweise dazu, Ressourcen freizugeben, die während des try-Blocks in Anspruch genommen wurden, aber von JavaScript nicht automatisch freigegeben werden (beispielsweise eine Blob-URL).
Die throw-Anweisung
Die Anweisung throw dient dazu, den Programmablauf zu unterbrechen. Ohne ein try..catch
-Konstrukt führt sie dazu, dass die Verarbeitung des laufenden Scripts beendet wird.
Sie müssen der throw-Anweisung ein Argument übergeben. Dabei kann es sich um jeden beliebigen JavaScript-Wert handeln, sogar undefined
ist erlaubt. Sie sollten aber nach Möglichkeit ein Error-Objekt dafür verwenden, weil JavaScript darin automatisch Informationen zum Entstehungsort des Fehlers ablegt (was zwar nicht Teil des ECMAScript-Standards ist, aber von allen Browsern gemacht wird).
Wenn JavaScript auf eine throw
-Anweisung stößt, beginnt es, nach einem passenden catch
-Block zu suchen. Dazu untersucht es innerhalb der aktuellen Funktion alle Anweisungsblöcke, in denen sich die throw
-Anweisung befindet, in ihrer Schachtelung von innen nach außen. Ist einer davon ein try
-Block, beginnt die Fehlerbehandlung.
- Ist ein
catch
-Block vorhanden, wird der Fehlerzustand gelöscht und die Ausführung imcatch
-Block fortgesetzt. Dieser kann auf den Fehler reagieren und bei Bedarf einen neuen Fehler auslösen, oder auch den alten Fehler erneut werfen. - Ist ein
finally
-Block vorhanden, wird er im Anschluss an die try- und catch-Behandlung auf jeden Fall ausgeführt, auch dann, wenn imcatch
-Block wieder ein Fehler geworfen wurde. Imfinally
-Block steht das Fehlerobjekt nicht zur Verfügung.
Besteht nach Abschluss von catch
und finally
ein ungefangener Fehler (d.h. catch
oder finally
haben einen neuen Fehler geworfen), wird die Suche nach einem try
-Block, der den Fehler behandeln kann, fortgesetzt.
Wenn dem try
-Block außerdem ein finally
-Block zugeordnet ist, wird dieser im Anschluss an den catch
-Block ausgeführt, ganz gleich, ob im catch
-Block ein neuer Fehler geworfen wurde oder nicht.
- Der
try
-Block besitzt nur einenfinally
-Block. In diesem Fall wird derfinally
-Block ausgeführt, der Programmablauf wieder unterbrochen
Anwendung des try..catch-Statements im Beispiel
Im ersten Teil der Anweisung wird geprüft, ob die Variable x
den Wert 2 oder 3 besitzt. Je nach Ergebnis werden mit throw
verschiedene Fehlerwerte definiert. Hat x
den Wert 2, so wird der "Fehler" mit dem Wert richtig generiert. Hat sie den Wert 3 so erhält der Fehler den Wert falsch. Weitere Fehlervarianten werden nicht behandelt.
Im ersten Durchlauf existiert die Variable x
im Beispiel noch gar nicht, da sie ja erst nach 200 Millisekunden existiert. Sie kann damit weder den Wert 2 noch den Wert 3 besitzen. In der nachfolgenden Fehlerbehandlungsroutine catch(e)
wird geprüft, ob einer der definierten Fehler, also richtig oder falsch, aufgetreten ist. Zunächst ist das offensichtlich nicht der Fall. Die Anweisungen, die von den "Fehlerwerten" richtig und falsch abhängig sind, werden deshalb nicht ausgeführt. Die finally
-Anweisung wird dagegen in jedem Fall ausgeführt. Sie bewirkt im Beispiel, dass der übergebene Parameter Zaehler
um 1 erhöht wird.
Gesamtkontrolle
Am Ende ruft sich die Funktion teste_x()
mit setTimeout()
um 30 Millisekunden zeitverzögert selbst wieder auf. So behält sie die Kontrolle über das Geschehen, bis ein definierter Zustand eintritt. Der Parameter Zaehler
wird dabei mit Hilfe einer Zeichenkettenverknüpfung übergeben.
Interessant wird es, wenn der Zeitpunkt erreicht ist, zu dem die Variable x
existiert. In diesem Fall tritt einer der vordefinierten Fälle ein. Da x
den Wert 3 besitzt, wird der throw
-Fehler mit dem Wert falsch generiert (dies soll im Beispiel einfach zeigen, dass throw
zur Erzeugung von Werten gedacht ist, die durchaus und oft auch Fehlerzustände bezeichnen). Im nachfolgenden catch(e)
-Block führt dies dazu, dass die Funktion zeigeErgebnis()
aufgerufen wird. Im Beispiel wird für beide definierten throw-Werte die gleiche Funktion aufgerufen. Sie können an dieser Stelle jedoch auch völlig verschiedene Anweisungen notieren. Jede dieser Fehlerbehandlungsroutinen bricht gleichzeitig die Funktion teste_x()
mit return ab, da x
ja nun existiert und der "kritische Zustand" beendet ist.
Ausgabe des Ergebnisses
Die Funktion zeigeErgebnis()
erhält als Parameter die Variablen Zaehler
und Ergebnis
übergeben. In der Variablen Zaehler
ist die Anzahl der Durchläufe bis zur Existenz der Variablen x
gespeichert und in der Variablen Ergebnis das Resultat der Fehlerbehandlung. Mit alert()
wird im Beispiel zur Kontrolle ausgegeben, wie viele Durchläufe benötigt wurden und was für ein Ergebnis erreicht wurde.
Anwendungsfälle
Prüfungen mit dem try..catch-Statement sind z. B. dann sinnvoll, wenn Sie wie im Beispiel mit setTimeout()
zeitversetzte Aktionen ausführen und davon abhängige Anweisungen ausführen wollen. Ebenfalls sinnvoll ist das Statement, wenn Sie z. B. auf Variablen oder Funktionen zugreifen wollen, die in anderen Frame-Fenstern notiert sind, wobei das Script nicht wissen kann, ob die Datei im anderen Frame-Fenster, in der das entsprechende Script notiert ist, bereits eingelesen oder überhaupt die dort aktuell angezeigte Seite ist.