JavaScript Promises

Wozu Promises?

JavaScript ist single threaded. Das bedeutet, dass alles im gleichen Thread läuft und zwei Teile Code nicht gleichzeitig ausgeführt werden können. In Browsern teilt sich JavaScript diesen Thread in der Regel auch noch mit einer Reihe anderer Dinge, um die sich der Browser kümmert. Mit welchen anderen Vorgängen sich JavaScript diesen Thread teilt, ist abhängig vom jeweiligen Browser. In der Regel handelt es sich aber um Dinge wie Usereingaben, Zeichnen der Oberfläche, etc. Wenn wir nun Code haben, der auf Ergebnisse wartet, und deswegen den Thread blockiert, dann können auch alle anderen Dinge nicht ausgeführt werden. Das stört!

Eine alternative Lösung für das Problem sind Events:

Dieser Code würde nicht blockieren. Es wird ein Verweis auf das Bild geholt, und ein Listener an dessen Events gehängt. Der Code wird dann nicht weiter ausgeführt, bis einer dieser Events eintritt. Leider kann es passieren, dass die Events schon ausgelöst wurden, bevor wir auf diese gelauscht haben. Also müssen wir auch noch mal die „complete“-Eigenschaft des Objekts überprüfen.

Und auch das würde nicht für Bilder funktionieren, welche schon einen Fehler werfen, bevor wir an seine Events kommen. Und wenn es nicht um ein Bild, sondern um viele geht, wird alles noch viel komplexer.

Was man eigentlich möchte, wäre etwas wie dies:

und…

Und genau das machen Promises!

und…

So gesehen sind Promises ein wenig wie Events, allerdings:

  • Ein Promise kann nur einmal erfolgreich oder mit Fehler beendet werden. Es kann auch nicht von erfolgreich auf fehlerhaft wechseln und umgekehrt. Events können beliebig oft für ein Objekt gefeuert werden.
  • Wenn ein Promise bereits beendet ist und später Callbacks für erfolgreich und fehlerhaft hinzugefügt werden, dann werden die richtigen Funktionen aufgerufen, selbst wenn das Event schon lange gelaufen ist.

Promise erstellen

Die Idee hinter einem Promise ist, dass es das Ergebnis einer asynchronen Operation repräsentieren soll.

Ein Promise kann dabei folgende Status haben:

  • fulfilled – die Operation wurde erfolgreich ausgeführt
  • rejected – die Operation ist an mindestens einem Punkt gescheitert
  • pending – die Operation ist noch nicht beendet
  • settled – das Promise hat den Status fulfilled oder rejected

Und so wird ein Promise erstellt:

Der Konstruktor des Promise nimmt einen Parameter an, eine Callback-Funktion mit wiederum 2 Parametern. Wenn alles gut läuft, dann wird resolve aufgerufen, ansonsten reject.

Promise benutzen

Die .then-Funktion hat 2 Parameter: Eine Funktion, die im Erfolgsfall aufgerufen wird, und eine, die aufgerufen wird, wenn die Operation fehlgeschlagen ist. Beide sind optional. Es ist also möglich, nur den einen Fall zu behandeln und sich um den anderen nicht weiter zu kümmern.

Des Weiteren ist es auch möglich, die thens zu verketten:

Man sieht also, die Ergebnisse der einzelnen, möglicherweise asynchronen Operationen werden bei einer Verkettung einfach durchgereicht.

Fehlerbehandlung

Wie schon gesagt, die then-Funktion kann 2 Callbacks aufnehmen: Eine für den Erfolgsfall, die andere wird im Fehlerfall aufgerufen.

Stattdessen kann aber auch „catch“ benutzt werden.

Catch ist nur eine bequeme Variante von .then(undefined, func). Deswegen ist das zweite Beispiel das gleiche wie

Das ist ein kleiner, aber dennoch entscheidender Unterschied zu .then(func,func). Die rejects vom Promise werden weiter gereicht, bis zum nächsten .then mit einer Callback Funktion für den Fehlerfall.
Für das zweite then in diesem Beispiel bedeutet dies, dass sowohl die erste Funktion als auch die zweite Funktion aufgerufen werden könnten, nämlich wenn das erste then fehlschlägt, und das zweite zum Erfolg führt.
Dies im Hinterkopf lassen sich so auch spannende Verkettungen für den Fehlerfall erstellen.

In diesem Beispiel sieht man, dass wenn alles gut läuft, die Reihenfolge der Funktionsaufrufe folgendermaßen ist:
Async1, Async2, Async3, wennAllesFertig

Nehmen wir an, Async2 schlägt fehl und ruft reject auf:
Asyn1, Async2, AsyncErrorHandling, wennAllesFertig

Man sieht also, die Kette wird solange durchlaufen, bis sich eine Funktion um das reject kümmert. Dies muss wie gesagt nicht catch sein, sondern kann auch ein .then(undefined, func) sein.

Verfasst von Dennis Otten am 25. Oktober 2016