JavaScipt-Code

8. August 2014 von & gespeichert unter Coding.

Closures war das bestimmende Thema meines vorherigen Blog-Posts, in dem ich die Vorzüge der Funktion als ein Objekt höherer Ordnung darstellte. Der Quellcode kann dadurch schlanker sein und auch die Kapselung von Klassen geht damit leicht von der Hand. Teil drei, und damit der letzte Teil in meiner Reihe (hier ist Teil 1 und Teil 2) über die JavaScript-Funktion, beschäftigt sich diesmal mit vollmundigen Versprechungen: den Promises (oder auch Futures). Grob zusammengefasst ist ein Promise ein Wrapper, der möglicherweise erst in der Zukunft einen Wert enthält. Aber warum möchte man das und wie funktioniert das?

Aus der Callback-Hölle

JavaScript hat ein recht simples Ausführungsmodel, alles hört auf den Event-Loop, der i.d.R. bis zu 60 mal pro Sekunde ausgeführt wird, und dieser läuft in einem einzigen Thread. Daraus resultiert natürlich, dass nebenläufige oder parallele Prozesse nicht ohne Weiteres möglich sind.1 Das ist auch gar nicht notwendig, denn JavaScript arbeitet im Bezug auf Input/Output nicht-blockierend. Das heißt, dass Event-Handler sowohl den Input als auch die Resultate des Outputs behandeln. Sende ich also eine Ajax-Anfrage mit jQuery, unterbricht die Ausführung nicht und geht erst mit dem Ergebnis der Anfrage weiter, sondern ich muss ein Callback angeben, das die Antwort entgegennimmt. In der Zwischenzeit kann also ganz anderer Code ausgeführt werden, das Ganze läuft also asynchron.

So toll das auch sein mag, hat das einen entscheidenden Nachteil: Sind mehrere aufeinanderfolgende asynchrone Anfragen notwendig, verschachteln sich die Callbacks gerne mal sehr tief. Das mindert die Lesbarkeit des Quelltextes und erhöht durchaus auch Fehler aufgrund der unterschiedlichen Bindungen der einzelnen Closures (siehe nochmals den vorangegangenen Blog-Post). So etwas wird auch als Callback-Hell bezeichnet. Das folgende Code-Listing zeigt das einmal beispielhaft mit der Facebook-API:

So, oder so ähnlich, kann ein Code-Schnipsel für eine Facebook-App aussehen. Leider ist das Konstrukt nicht leicht zu lesen und zusätzlich gibt es mehrere Stellen, die die gleiche Funktionalität fordern. Wie kann man das also verbessern? Eine Lösung ist, eine Sequenzierung einzuführen. Und in diesem Fall kommen Promises ins Spiel. Dazu empfiehlt sich z.B. die q-promises-Bibliothek. Die README sowie das Wiki des Projekts bieten einen sehr guten Einstieg.

Von vollmundigen Versprechungen

Soll eine asynchrone Aktion durchgeführt werden, wird zunächst ein sogenanntes „Deferred Object“ (engl. aufgeschobenes Objekt) erzeugt. Dieses enthält das letztendliche Promise und zwei wesentliche Funktionen: resolve( value ) und reject( reason ). Sobald der Browser die entsprechenden Events sendet, kann die entsprechende Antwort an das „Deferred“ weitergeleitet werden. Bei einer positiven Antwort, wird die resolve-Methode mit der Antwort als Wert aufgerufen, bei einer negativen Antwort wird die reject-Methode mit dem Fehler bzw. einer Fehlerbeschreibung als String aufgerufen. Das Promise-Objekt hat im Wesentlichen die then-Methode (promise.then( onResolved, onRejected, onNotified)). Diese wird mit bis zu drei Callbacks als Argumente aufgerufen und gibt jeweils wiederum immer ein Promise-Objekt zurück. Dadurch lassen sich Aktionen bequem verketten. Der erste Callback (onResolved) soll ein positives Ergebnis des asynchronen Aufrufs entgegennehmen. Der zweite Callback (onRejected) wird bei einem negativen Ergebnis ausgeführt. Der letzte Callback (onNotified) ist der einzige, der mehrfach aufgerufen werden kann. Dadurch kann man z.B. den Fortschritt bei einem Datei-Upload abbilden. Nun aber ein Code-Beispiel:

Die Arbeit mit dem „Deferred Object“ kann man sich dank der q-Bibliothek auch ersparen, da sie jQuery-Ajax-Requests entsprechend behandeln kann. Die ajax-Methode von jQuery gibt ebenfalls Promises zurückgeben, nur sind diese nicht ganz so mächtig. Ein Video beleuchtet den Unterschied genauer, weshalb ich hier nicht näher darauf eingehen werde. Wie kann man also unsere request-Funktion kürzer schreiben:

Das ist doch gar nicht schlecht, oder? Wir überlassen es der q-Bibliothek, wie sie den Ajax-Request auswertet und kümmern uns nur noch um die Handler-Funktionen. Wichtig dabei: Die Rückgabewerte der Handler werden unverändert an den nächsten Handler weitergegeben, sofern sie selbst kein Promise sind. Gehen wir nun den entscheidenden Schritt weiter und schauen uns an, wie wir unseren Facebook-Code aufräumen und übersichtlicher gestalten können. Zunächst brauchen wir sogenannte Wrapper-Funktionen, die die Aufrufe der Facebook-Methoden in Promises umwandeln:

Das mag zwar deutlich mehr Code sein als noch im Eingangs-Beispiel, der Vorteil ist nun aber, dass die Angabe eines Callbacks nicht mehr benötigt wird und jede Funktion sofort ein Promise zurückgibt. Nun soll natürlich nicht die Implementierung der Sequenzierung unserer Facebook-Anfrage fehlen:

Das sieht doch gleich viel aufgeräumter aus, oder? Wir haben einen klaren und leicht lesbaren Kontrollfluss und doppelte Aufrufe werden vermieden. Der Quelltext ist somit leichter wartbar und ein neuer Entwickler kann sich recht schnell zurechtfinden.

Zusätzlich sind einige Schmankerl der q-promises enthalten. So weiß die Bibliothek, wie es mit den Rückgabe-Werten umgehen soll. Im ersten then-Handler sind im if-else-Statement drei entsprechende Fälle zu sehen. Fall eins gibt normal das Argument zurück, die q-Bibliothek reicht dieses an den nächsten Handler weiter. Fall zwei wirft einen Fehler, dieser wird von der q-Bibliothek abgefangen und an den nächstmöglichen Fehler-Handler weitergeleitet. In diesem Fall würden sämtliche anderen Handler ignoriert und komplett zur onError-Funktion gesprungen. Diese wird dann mit dem geworfenen Fehler als Argument aufgerufen. Der Fehler kann abgefangen werden und eine völlig andere Sequenz von Aktionen zur Folge haben. Der dritte Fall gibt ein neues Promise-Objekt zurück, dessen Sequenz die q-Bibliothek erst abarbeitet, bevor sie die übergeordnete Sequenz weiterverfolgt.

Was wir hier bisher sehen, sind nur ein paar Aspekte von Promises. Sie sind noch weitaus vielfältiger und ein Blick in die Dokumentation lohnt sich. Sie sind auch ein weiteres Beispiel für die Vielseitigkeit der JavaScript-Funktion und eine Brücke hin zur funktionalen Programmierung, in der Konstrukte wie Promises ein wesentlicher Bestandteil sind.

Persönlich habe ich Promises schon lange im Einsatz. Nutzt ihr sie ebenfalls? Was sind eure Erfahrungen? Oder habt ihr vielleicht andere oder gar bessere Ansätze? Berichtet mir von euren Erfahrungen.

Damit schließe ich meine Artikel-Reihe zur JavaScript-Funktion. Ich hoffe, es hat euch gefallen und ihr konntet neue Inspiration für eure tägliche Arbeit daraus ziehen. Bei Kritik und Anregungen schreibt einfach einen Kommentar. Derweil werde ich meine magische Kugel nach den nächsten Themen befragen. Bis dahin: Stay tuned!


  1. Die sogenannente Web-Worker sind eine Möglichkeit, Code außerhalb des Event-Loops ausführen zu lassen

über den Autor

krone@pluspol.info'
Stefan ist leidenschaftlicher Gamer, Film- sowie Musikliebhaber und begeisterter Entwickler. Sein Interesse gilt den neuesten Technologien. Seine stete Unzufriedenheit mit dem Status Quo treibt ihn an, besser zu werden.



Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.