Javascript Promise – wenns funktioniert hat, dann … sonst

Ein Promise-Objekt ist ein Versprechen: Wenn die Zusage erfüllt wird (der Code funktioniert), ist das Ergebnis der Zusage ein Wert, wenn nicht, gibt das Promise-Objekt ein Fehler-Objekt zurück.

Javascript Promise ist mit ECMAScript 6 eingezogen, macht den Umgang mit asynchronem Verhalten sicherer und durch die then-Funktion lesbarer als Callback-Funktionen.

Asynchron bedeutet, dass zwischen einer Anfrage und der Antwort eine Verzögerung oder Wartezeit von unbestimmter Dauer liegt.

18-12-15 SITEMAP CSS HTML JS Basis JS Web Tutorial SVG

Callbacks bei zeitlich unbestimmbaren Abläufen

Wenn wir auf einen zeitlich nicht absehbaren Ablauf angewiesen sind – z.B. beim Laden eines Bildes oder einer json-Datei – werden Callbacks eingesetzt, die beim Eintreten eines Ereignisses aufgerufen werden.

Da die Callback-Funktion i.d.R. nur einmal aufgerufen wird, sehen wir Callbacks oft als anonyme Funktion. Promises ergänzen oder ersetzen Callbacks und sind syntaktisch prägnanter sind als Rückrufe. Sie geben dem Code eine besser lesbare Struktur und zusätzliche Garantien, die den asynchronen Ablauf verläßlicher machen.

Asynchrone Events und das Warten

Wenn Daten geladen werden, dauert das Warten immer eine unbestimmte Zeit. Wenn nur ein Ladevorgang beobachtet werden muss, ist alles noch einfach. Aber was ist, wenn nach dem Laden der JSON-Datei auf eine weitere Aktion gewartet werden muss, z.B. weil die URL der zweiten JSON-Datei in der ersten URL steht?

const url = "file.json";
const xhr = new XMLHttpRequest(url);
xhr.addEventListener ("readystatechange", (url) => {
	if (xhr.readyState === 4) {
		console.log (xhr.responseText);
	}
})
xhr.open ("GET", url);
xhr.send();
const url = "../fetch.json";
const url2 = "../file.json";
const url3 = "../data.json";
const xhr = new XMLHttpRequest(url);
xhr.addEventListener ("readystatechange", (url) => {
	if (xhr.readyState === 4) {
		console.log (xhr.responseText);
		const xhr2 = new XMLHttpRequest(url2);
		xhr2.addEventListener ("readystatechange", (url2) => {
		if (xhr2.readyState === 4) {
			//console.log ("xhr2 " + xhr2.responseText);
			const xhr3 = new XMLHttpRequest(url3);
			xhr3.addEventListener ("readystatechange", (url3) => {
				//console.log ("xhr3 " + xhr3.responseText);
			});
			xhr3.open ("GET", url3);
			xhr3.send();
		}
		
	});
	xhr2.open ("GET", url2);
	xhr2.send();
}});
xhr.open ("GET", url);
xhr.send();

Javascript Promise

Mit verschachtelten asynchronen Events kommen wir in Teufels Küche, die auch als Callback Hell bezeichnet wird. Auch jQuery und Javascript fetch würden dieses abgrundtiefe Loch verschachtelter asynchroner Aufrufe nicht wirklich vereinfachen.

Promises sind Objekte, die das Ergebnis einer asynchronen Aktion abfangen und es zurück geben, wenn die versprochenen Daten erfolgreich geladen wurden oder ein Fehler aufgetreten ist. Sie sind sozusagen Platzhalter für eine Zusage, die vielleicht erfüllt wird, vielleicht aber auch nicht.

let p = new Promise ( function (resolve, reject) {
	if (alle Aufgaben erfüllt) {
		resolve ();
	} else {
		reject (err);
	}
});

Promises sind eine Schutzmaßnahme. Sie werden niemals vor dem Ende des Wartens auf ein Event aufgerufen und sie werden nur einmal ausgeführt. Gleich ob ein Promise erfüllt werden kann oder nicht: Es ruft auf jeden Fall die korrekte Callback-Funktion auf. Darüber hinaus können Callbacks miteinander verketted (chained) werden: Dann ruft das Ergebnis einer Operation am Ende die nächste Operation auf.

Promise und then ()

Ein einfacher XMLHttpRequest mit nur einem asynchronen Request ist übersichtlich, aber sobald ein weiterer Request auf dem ersten Request beruht, wird der Code verschachtelt und unübersichtlich.

Promises stellen eine Methode then () zur Verfügung, die ausgeführt wird, nachdem das Versprechen eingelöst wurde. then () enthält zwei optionale Argumente: ein Callback für den Erfolg, ein Callback für den Fehlerfall.

p.then ( function (result) {
	// Promise erfüllt
}, function (err) {
	// Promise zurückgewiesen
})

Wenn das Promise erfüllt ist, wird die erste Funktion mit dem übergebenen result-Objekt ausgeführt. Anderenfalls wird die Rückweisung (reject) in der zweiten Funktion mit dem übergebenen error-Objekt ausgeführt.

So kann z.B. eine komplizierte Aktionsfolge aussehen, bei der zwei JSON-Dateien geladen werden sollen, wobei das Auslesen der zweiten JSON-Datei nur Sinn macht, wenn das erste JSON korrekt angeliefert wurde.

fetch (url).then ( function (response) {
	return response.json();
}).then ( function (data) {
	console.log ("url 1 " + data)
}).then (fetch (url2).then (function (response) {
	return response.json();
}).then (function (data) {
	console.log ("url 2 " + data)
}).then (fetch (url3).then (function (response) {
	return response.json();
}).then (function (data) {
	console.log ("url 3 " + data)
})
))

Da haben wir ein einfaches Muster: then gefolgt von einem return, then gefolgt von einem return.

then-Aufrufe können sequentiell aneinander gehangen werden, um zusätzliche asynchrone Aktionen nacheinander auszuführen (chaining).

Alternativ kann das Callback aufgesplittet werden und catch übernimmt das Zurückweisen des Promise. Die Methoden können verkettet und sequentiell ausgeführt werden. Wenn etwas von then zurückgegeben wird, wird es dem nächsten then übergeben. Wird ein Promise zurückgegeben, wartet das nächste then auf das erste Promise, bevor die Ausführung beginnt. In diesem Beispiel ist übrigens kein catch-Block mit im Spiel – auch das geht ohne Weiteres, denn das catch ist optional. Es sollte nur dafür gesorgt werden, dass die Anwendung »sanft« verweigert.

Promise vs Event Listener

Promises ähneln Event Listenern mit ihrem asynchronen Callback in gewisser Hinsicht. Wir legen fest, was bei der Erfüllung oder bei einem Fehler passieren soll und der Call Back wartet auf den Ausgang des Promise.

Der Unterschied: Während Events mehrmals feuern können, findet ein Promise nur einmal statt.

Browser-Unterstützung für promise

Sowohl promise als auch fetch werden von den immergrünen Browsern seit geraumer Zeit unterstützt und wie immer steht IE11 außen vor. Promise und Fetch sind keine Konstrukte der Programmiersprache, also muss nicht gleich ein Transpiler her, sondern Polyfills reichen schon.