Asynchrone Programmierung mit Promises

Seite 2: Fehlerbehandlung

Inhaltsverzeichnis

Um an der Stelle (noch) nicht in technische Details abzutauchen, zeigt der folgende Quelltextauszug die weiter oben durchgeführte asynchrone Operation unter Zuhilfename von Promises.

var endpoint = 'https://api.github.com/repositories';

jQuery.get(endpoint)
.then(function(repositories) {
// Beschraekung auf 10 repositories, damit
// das GitHub rate limit nicht
// ueberschritten wird
var promises = repositories.slice(0, 10)
.map(function(repository) {
var url = endpoint + '/' + repository.id;
return jQuery.get(url)
.then(function(details) {
console.log('%s uses %s',
details.full_name,
details.language);
});
});
return jQuery.when.apply(jQuery, promises);
})
.then(function() {
console.log('Retrieved details for all repos!',
arguments);
})
.fail(function() {
console.error('Error: ', arguments);
});

jQuery unterstützt Promises seit Version 1.5 nativ. Da die Funktion jQuery.get ein Promise-Objekt zurückliefert, lässt sich wieder auf diese kompaktere Funktion zurückgreifen (zur Erinnerung: jQuery.get erlaubt keine Fehlerbehandlung mittels Callbacks und konnte daher zuvor nicht genutzt werden).

Der größte Unterschied zur vorherigen Version sind die then(...)-Aufrufe, durch die man typische Ergebnis-Callbacks registriert. Letztere sind für den sogenannten "Happy Path" zuständig, also den Programmpfad, der sich nicht mit Fehlern oder Ausnahmen beschäftigt.

Zwei Callbacks werden für erfolgreiche Requests registriert. Dazu gibt es eine allgemeine Fehlerbehandlung, die zum Einsatz kommt, sollte in einer der beiden Operationen ein Fehler auftreten. Ein Promise-Objekt kann sich im Übrigen immer in einem von drei Zuständen befinden. Der initiale Status ist "unfulfilled" und besagt, dass das Ergebnis der Operation noch nicht zur Verfügung steht. Von dem Status aus kann ein Promise-Objekt entweder in den "fulfilled"- oder "rejected"-Zustand übergehen, der jeweils das erfolgreiche respektive nicht erfolgreiche Ende eines Vorgangs darstellt. Sobald sich das Promise in einem der beiden zuletzt genannten Zustände befindet, kann es sich nicht mehr verändern. Das gilt sowohl für den Status eines Promise als auch für dessen Wert. Letzterer stellt das Resultat der Operation (etwa den Body eines HTTP-Requests) oder den Fehlergrund dar (beispielsweise ein Error-Objekt).

Es ist kaum ersichtlich und doch ein Vorteil gegenüber der Callback-Variante: Der Bezug der Repository-Details läuft ebenfalls über Promises. Die Variable promises ist ein Array von Promises, wobei jedes das Beziehen der Details eines Repositories abbildet. Mit Hilfe der jQuery-Funktion jQuery.when ist es dann möglich, ein Promise-Objekt zu erzeugen, das den Status aller übergebenen Promises kapselt. Daher ist, wenn eines mit Detailbezug rejected ist, auch das aggregierte Promise rejected. Das Schöne daran ist, dass sich mit solch einem Konstrukt auf einfache Art und Weise die Ausführung mehrerer gleichzeitiger asynchroner Anfragen überwachen lässt (das Beziehen der Details aus den Repositories läuft parallel ab).

Das aggregierte Promise wird anschließend zurückgeliefert ("returned") und, so bald es in den Status fulfilled wechselt, also alle Repository-Detail-Promises erfüllt wurden, der zweite then()-Block aufgerufen. Asynchrone Operationen lassen sich also über then-Aufrufe verketten, indem in einem then-Block ein Promise-Objekt zurückgeliefert wird. Fehler, die beim Laden der Repository-Übersicht oder den dazugehörigen Details auftreten, bearbeitet die über fail registrierte Fehlerbehandlungsroutine. Sie ist übergreifend zu sehen.

Mit der Standardisierung von JavaScript-APIs befasst sich mit das CommonJS-Projekt. Der Fokus liegt dabei insbesondere auf serverseitigem JavaScript, das durch den Einsatz von standardisierten APIs auf unterschiedlichen JavaScript-Implementierungen wie Node.js und Rhino lauffähig sein soll. Neben bereits bestätigten gibt es Vorschläge für eine Promises-Spezifikation. Am verbreitetsten ist sicherlich der Entwurf Promises/A von Kris Zyp, der Promises, deren Status und die Funktion then beschreibt. Leider ist die Dokumentation zu Promises/A sehr knapp ausgefallen, was zur Folge hat, dass es zahlreiche unterschiedliche Implementierungen gibt (insbesondere mit Blick auf die Funktionsweise). So unterstützt die jQuery-Implementierung auch in Version 2.0.3 noch keine Exceptions in then-Handlern, was zur Folge hat, dass die Fehlerbehandlung mittels Promises nicht garantiert ist.

Der folgende Code (unter der Annahme, dass sich die Datei dummy.json laden lässt) ruft nicht die definierte Fehlerbehandlungsroutine auf. Stattdessen wandert der Fehler ungehindert den Call-Stack hinauf, sodass ihn die globale Fehlerbehandlung abarbeiten muss.

jQuery.get('dummy.json')
.then(function() {
throw new Error('Could not process...');
})
.fail(function() {
console.error('Fail handler:', arguments);
})
.done();