Asynchrone Programmierung mit Promises

In JavaScript-Programmen informieren Callback-Funktionen häufig über Erfolg und Ergebnis einer Operation. Das kann allerdings schnell zu unübersichtlichem Code führen. Promises bieten einen Ansatz, um der "Pyramid of Doom" zu entkommen.

In Pocket speichern vorlesen Druckansicht 3 Kommentare lesen
Lesezeit: 17 Min.
Von
  • Ben Ripkens
Inhaltsverzeichnis

Callback-Funktionen dienen in JavaScript dazu, über Erfolg und Ergebnis einer Operation zu informieren. Da solch eine Herangehensweise allerdings schnell zu wenig übersichtlichem Code führen kann, bietet es sich an, nach Alternativen zu suchen.

JavaScript-Programme laufen in der Regel innerhalb eines einzelnen Prozesses. Ein- und Ausgabeoperationen führen sie aus dem Grund meist asynchron aus. Bibliotheken wie jQuery und die Node.js-Standardbibliothek machen es vor: Nur der Programmcode stößt beispielweise Anfragen an externe Dienste oder Lese- und Schreiboperationen an. Eine Callback-Funktion informiert das Programm im Anschluss über den Erfolg der Operation und deren Resultat (zum Beispiel den Inhalt einer Datei). Einen typischer Aufruf einer solchen Operation zeigt der folgende Codeausschnitt:

fs.readFile('foo.log', 'utf8', function(err, data) {
// wird aufgerufen nach dem erfolgreichem lesen
// der Datei
});

Die Programmierung mit Callbacks wird jedoch recht schnell unübersichtlich. So kommt es typischerweise zu Problemen, wenn das Resultat einer asynchronen Operation eine weitere asynchrone Operation anstößt. Naive Programmierung resultiert häufig in der sogenannten "Pyramid of Doom":

var endpoint = 'https://api.github.com/repositories';
jQuery.ajax({
url: endpoint,
success: function(repositories) {
// Beschraekung auf 10 repositories, damit
// das GitHub rate limit nicht
// ueberschritten wird
repositories.slice(0, 10)
.forEach(function(repository) {
jQuery.ajax({
url: endpoint + '/' + repository.id,
success: function(details) {
console.log('%s uses %s',
details.full_name,
details.language);
},
error: function() {
console.error('Fehler', arguments);
}
});
});
},
error: function() {
console.error('Fehler', arguments);
}
});

Das Beispiel nutzt jQuery, um von GitHub eine Liste von Repositories zu beziehen. Zu jedem Repository ist zusätzlich die genutzte Sprache auszugeben. Diese Information ist jedoch nicht Teil des Endpunkts der repositories-API. Folglich werden die Details jedes Repositorys über einen weiteren API-Aufruf bezogen und auf der Konsole ausgegeben. Zur korrekten Fehlerbehandlung ist leider auf jQuery.ajax zurückzugreifen, da jQuery.get keine Callbacks nutzende Fehlerbehandlung anbietet.

Zugegeben, die dargestellte Pyramid of Doom ist noch einfach nachzuvollziehen, aber zu bedenken bleibt, dass viele Projekte eine komplexere Programmlogik benötigen. So kann es gut sein, dass bestimmte Operationen nur ausgeführt werden, wenn eine definierte Bedingung eintritt (zum Beispiel soll das Programm den Projektnamen in eine Datei schreiben, wenn ein Projekt JavaScript als Hauptsprache nutzt). Es stellt sich dann häufig die Frage, wie man den Code leserlich und wartbar halten kann.

Eine Möglichkeit ist, benannte Methoden an Stelle anonymer Callbacks zu nutzen. Mit einer guten Namensgebung lässt sich der Code hinsichtlich der Lesbarkeit deutlich verbessern. Leider erleichtert diese Vorgehensweise jedoch nicht die Fehlerbehandlung. So sind die möglichen Fehler weiterhin in jedem Callback zu behandeln. Es existiert keine native Möglichkeit einer übergreifenden Fehlerbehandlung.

Eine Alternative zur klassischen, Callbacks verwendenden Programmierung, die sich immer größerer Beliebtheit erfreut, ist der Einsatz von Promises. Promises, oder auch Deferreds oder Futures, repräsentieren das potenzielle Resultat einer asynchronen Operation und lassen sich, wie andere Werte, einer Variable zuweisen oder als Parameter einer Funktion übergeben.