⬅ Zurück zur Übersicht

Javascript Promises

david am Samstag, 12.05.2018 - 04:27:01
⬅ Zurück zur Übersicht

Seit Juni 2015 sind Promises mit ES6 auch in Javascript angekommen und können hier nativ genutzt werden. Vielleicht geht es einigen hier so wie mir am Anfang und wissen nicht so recht, für was promises sinnvoll sind oder wie sie genutzt werden.

Was sind Promises?

Javascript ist „Singlethreaded“ und arbeitet daher sequenziell bzw. synchron.
Promises führen jetzt kurz gesagt mehrere Funktionen nacheinander aus, genauer: Wir können Funktion 2 genau dann aufrufen, wenn Funktion 1 abgeschlossen wurde. Wir können also zuerst eine Handlung ausführen, und erst wenn diese abgeschlossen ist die nächste Handlung starten. Sinnvoll ist das ganze zum Beispiel bei einem Ajax Request: Wenn wir die Verarbeitung der Daten erst dann starten wollen, sobald der Ajax Request abgeschlossen ist, können wir hierfür einen Promise nutzen, wobei zusätzlich danach unterschieden wird, ob der Promise Erfolg hatte oder einen Fehler hervorgerufen hat. Ohne Promise würde Javascript schon weiter machen, selbst wenn der Ajax – Request noch nicht abgeschlossen ist mit dem Resultat, dass unter Umständen der Rückgabewert noch leer ist.
Sinnvoll sind Promises daher nur bei asynchronen Aktivitäten – bei synchronen können wir uns auf die sequenzielle Verarbeitung von Javascript verlassen.
Synchroner Code ist sehr einfach – er wird von oben nach unten ausgeführt und Schritt 2 wartet immer erst, bis Schritt 1 abgeschlossen ist. Wir müssen also immer warte, bis eine Aktion abgeschlossen ist, um eine folgende Aktion auszuführen, was nervig ist und vor allem Zeit braucht, die am Ende dazu führt, dass das Skript umso länger braucht, bis es fertig ist. Schlimmer noch – wenn wir den Javascript – Code nicht am Ende der Seite platzieren, führt dies dazu dass die Ausführung des Javascript in Konkurrenz zum Rendern der Seite steht (Javascript, CSS, HTML etc. teilt sich den gleichen Thread – die Ausführung eines Javascripts blockiert also den weiteren Aufbau der Webseite) und somit der Seitenaufbau nur langsam voran geht. Das ärgert den Endnutzer, und verärgerte Endnutzer verärgern den Entwickler.

Zustände

Der Promise kann 4 Zustände haben:

 

Warum nicht if und if und if und if…?

Wir können natürlich auch den Code in eine Menge, Menge IF – Abfragen schachteln und bekommen dann die berüchtigte Pyramid of doom.
Mit Promises können wir das vermeiden.

Syntax

Die Syntax eines Promise sieht so aus

1
new Promise(function(resolve, reject) { /* CODE */ });

Im Code können jetzt die Funktionen / Argumente resolve() (führt zum Status „fulfilled“ und wird aufgerufen, wenn der Code keinen Fehler zurückgibt / der Promise erfolgreich war) und reject() (führt zum Status „rejected“ und wird aufgerufen, wenn der Code einen Fehler aufweist / der Promise nicht erfolgreich war) aufgerufen werden.

Basics

Ein kurzes Beispiel wäre entsprechend:

1
2
3
4
5
6
7
8
9
10
11
var promise = new Promise(function(resolve, reject) {
// Hier deinen Code einsetzen, der bei Erfolg success = true setzt
success === true ? resolve('Gut') : reject('Schlecht');
});
promise.then(function(result) {
console.log("Erfolg:");
console.log(result);
}, function(result) {
console.log("Fehler:");
console.log(result);
});

Solange dein Code ausgeführt wird, ist der Promise im Status „Pending“, sobald er abgeschlossen ist im Status „Settled“, und zwar entweder Fulfilled (wenn success === true) und resolve() wird aufgerufen, oder Rejected (wenn success === false) und reject() wird aufgerufen.
Der Promise Konstruktor bekommt also einen Callback mit zwei Parametern übergeben: Resolve (Handlung erfolgreich abgeschlossen) und Reject (Fehler trat auf). Sobald der Code abgeschlossen ist

1
(promise.then()

) wird je nach dem ob resolve() oder reject() aufgerufen wird entweder „Erfolg: Gut“ oder „Fehler: Schlecht“ ausgegeben.

Auf „Deutsch“: Die Variable promise enthält den Promise. Promise.then() bekommt als Parameter die zwei Funktionen, welche wir in „new Promise“ definiert haben – das entspricht dann also in etwa
promise.then(function resolve(result){}, function reject(result){});

Der Vorteil liegt jetzt darin, dass der resolve() und response() eben erst dann aufgerufen werden, wenn der Code im Promise ausgeführt wurde. Gerade bei Asynchronen Ajax – Requests ist das sinnvoll – so können wir die Daten aus dem Request erst dann verarbeiten, wenn der Request auch abgeschlossen wurde.

Asynchroner Beispielcode

Als Grundlage für alle folgenden Beispiele nehmen wir diesen asynchronen AJAX Request.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function GET(target) {
return new Promise(function(success, error) {
var request = new XMLHttpRequest();
request.open("GET", target);
request.addEventListener("load", function() {
if (this.status == 200) {
success('Erfolgreich!');
} else {
error(this.status);
}
}, false);
request.send(target);
});
}

Promising the promise

Um die Verwirrung perfekt zu machen, geben resolve() und response() ihrerseits wieder ein Promise zurück, wir können also mehrere Promises aneinander hängen und verketten.

1
2
3
4
GET("file1.html")
.then(GET("file2.html"))
.then(GET("file3.html"))
);

Wir laden also erst file1.html, danach file2.html, danach file3.html. Die „success“ – Funktion macht also nicht anderes, als wiederum einen Promise zu erstellen, jedoch mit einer anderen Datei.

Promise: It will be an array

Man muss nicht mehrere identische Promise aneinander hängen, um sie zu verketten. Dafür gibt es die Methode all() – diese nimmt mehrere Promise – Objekte als Array an und führt alle Funktionen in diesem Array als Promise aus.
Kleines Beispiel:

1
2
3
4
5
6
7
8
9
Promise.all([GET("file1.html"), GET("file2.htlm")]).then(
function (success) {
console.log(success);
},
function (error){
console.log("Es trat ein Fehler auf:");
console.log(error);
}
);

Alle im Array enthaltenen Funktionen werden jetzt dem Promise übergeben. Sobald ein Fehler auftritt, wird error() aufgerufen und erst wenn alle Funktionen erfolgreich ausgeführt wurden wird success() aufgerufen.
Alternativ können wir auch catch nutzen:

1
.catch(function(error) {});

Unser Beispiel sähe dann so aus:

1
2
3
4
5
6
7
Promise.all([GET("file1.html"), GET("file2.htlm")]).then(
function(success) {
console.log(success);
}).catch(
function(error) {
console.error(error);
});

Schnellstes Promise

Promise.race() gibt nur ein Promise zurück – und zwar das, was welches als erstes abgeschlossen wurde.

1
2
3
Promise.race([GET("smallfile.jpg"), GET("bigfile.jpg"), GET("extrasmallfile.jpg")]).then(
function (success) { console.log(success); }
);

Angenommen, Smallfile hat eine Größe von 200mb, extrasmallfile hat eine Größe von 2kb, und bigfile eine Größe von 3gb. In diesem Fall wird success() aufgerufen, und zwar nachdem die Datei extrasmallfile aufgerufen wurde. Dadurch dass alle 3 Funktionsaufrufe asynchron ablaufen, muss auch nicht bigfile auf smallfile oder extrasmallfile auf bigfile warten.

Kommentar schreiben

Kommentare