Benutzer:Rolf b/Fehlerbehandung

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

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.

Fehlermeldung mit try ... catch ansehen …
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.

  1. Ist ein catch-Block vorhanden, wird der Fehlerzustand gelöscht und die Ausführung im catch-Block fortgesetzt. Dieser kann auf den Fehler reagieren und bei Bedarf einen neuen Fehler auslösen, oder auch den alten Fehler erneut werfen.
  2. Ist ein finally-Block vorhanden, wird er im Anschluss an die try- und catch-Behandlung auf jeden Fall ausgeführt, auch dann, wenn im catch-Block wieder ein Fehler geworfen wurde. Im finally-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.

  1. Der try-Block besitzt nur einen finally-Block. In diesem Fall wird der finally-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.