Wie bereits zuvor erwähnt sind Promises ein Versprechen, dass etwas in er Zukunft fertig sein wird (Funktion).
In heutzutage modernen Browsern ist jede lang-laufende Operation hinter einem solchen Promise versteckt.
Promises sorgen dafür das eine Funktion (Message) in die Queue gesteckt wird und das der Event-Loop diese später ausführt.
Ein Promise zu erstellen ist relativ simpel
const myPromise = new Promise();
Dieses Promise macht nun aber nicht viel. Ein Promise benötigt eine Funktion die im Hintergrund ausgeführt wird.
const myPromise = new Promise(() => {
console.log('Hallo Welt!');
});
Wir sollten beim ausführen dann ein "Hallo Welt!" in den Developer Tools sehen
Wie sieht das nun aber alles aus wenn wir schauen wollen in welcher Reihenfolge das alles ausgeführt wird?
const myPromise = new Promise(() => {
console.log('Hallo Welt!');
});
console.log('Komme ich davor oder danach?');
Ein Promise macht auch nicht mehr als den Callback auszuführen und auf ein Resultat zu warten.
Wenn es nichts gibt auf das man warten muss, ist ein Promise auch sofort abgeschlossen.
Bevor wir weiter machen, gehen wir auf den Callback des Promises näher ein.
Dieser Callback erhält nämlich 2 Parameter, die wiederrum Callbacks sind
const p = new Promise((resolve, reject) => {
// ???
});
Oft werden diese 2 Callbacks als resolve
und reject
definiert.
Schauen wir mal, was die Variable als Inhalt hat, in der wir dieses Promise packt:
const myPromise = new Promise(() => {
console.log('Hallo Welt!');
});
// Promise { <state>: "pending" }
console.log(myPromise);
Das Promise hat einen Zustand der in "pending" hängt.
Dieser Zustand wird sich in dieser Form so auch nicht mehr ändern.
Hierfür sind die Callback-Funktionen resolve & reject
gedacht.
Rufen wir mal die Funktion resolve
innerhalb des Promises auf
const myPromise = new Promise((resolve) => {
console.log('Hallo Welt!');
resolve();
});
// Promise { <state>: "fulfilled", <value>: undefined }
console.log(myPromise);
Die Promise ist nun fulfilled und scheint einen value
zu beinhalten, der in diesem Fall undefined
ist.
Sehen wir uns noch kurz an, wenn der Promise rejected wird
const myPromise = new Promise((_, reject) => {
console.log('Hallo Welt!');
reject();
});
// Promise { <state>: "rejected", <value>: undefined }
console.log(myPromise);
Gut, wir wissen nun dass der Promise "rejected" wurde, aber was heißt das genau?
Nun, Promises funktionieren ein wenig anders als einfach aufgerufene Funktionen.
Wenn wir die Variable myPromise
untersuchen, sehen wir keine Möglichkeit, auf den Zustand oder den Wert zuzugreifen.
Aber es gibt ein paar Funktionen: then, catch & finally
.
Wir können nur auf Resultate und den Allgemeinen Ausgang eines Promise reagieren indem wir mit diesen Funktionen arbeiten.
Diese Funktionen sind chainable
. Wir können mehrere .then & .catch
nacheinander aufrufen.
Wenn ein Promise erfolgreich ist und resolve
aufruft, wird die Funktion in .then(() => {})
aufgerufen
Sollte das Promise nicht erfolgreich gewesen sein und hat reject()
aufgerufen, so wird .catch(() => {})
aufgerufen.
Wir können nicht aus einem Promise "ausbrechen". Wir können aber Resultate aus einem Promise zu jederzeit in den äußeren Scope verschieben und weitere Funktionen aufrufen.
Sehen wir uns das ganze nun in Aktion an:
const myPromise = new Promise((resolve, reject) => {
resolve();
});
myPromise.then(() => {
console.log('Das war erfolgreich!');
});
Der Fehlerfall ist auch leicht:
const myPromise = new Promise((resolve, reject) => {
reject();
});
myPromise.catch(() => {
console.log('Das war diesmal NICHT erfolgreich!');
});
Führen wir beide Varianten einmal zusammen:
const myPromise = new Promise((resolve, reject) => {
reject();
});
myPromise
.then(() => {
console.log('Das ging mal gut!');
})
.catch(() => {
// Durch das reject landen wir nur hier!
console.log('Das war diesmal NICHT erfolgreich!');
});
Das catch
muss aber nicht das Ende sein, wenn wir nach dem catch
noch etwas ausführen wollen, können wir einfach mit .then
weiter machen.
const myPromise = new Promise((resolve, reject) => {
reject();
});
myPromise
.then(() => {
console.log('Das ging mal gut!');
})
.catch(() => {
console.log('Das war diesmal NICHT erfolgreich!');
})
.then(() => {
console.log('Es gibt mehr danach!');
});
Das weitere .then
und .catch
aufgerufen werden können, hat einen entscheidenden Vorteil:
Wir können in .then
und .catch
einen Fehler werfen um ein anderes .catch auszulösen.
Gleichzeitig bedeutet ein nicht vorhanden sein eines Errors, dass das nächste .then
aufgerufen wird.
const myPromise = new Promise((resolve, reject) => {
resolve();
});
myPromise
.then(() => {
console.log('Das ging mal gut!');
throw new Error('Pech gehabt!');
})
.catch(() => {
console.log('Das war diesmal NICHT erfolgreich!');
})
.then(() => {
console.log('Es gibt mehr danach!');
});
Um am Ende auf jeden Fall eine Funktion auszuführen gibt es zu guter letzt noch die Funktion .finally
.
Der Aufruf sorgt dafür, dass in jedem Fall - egal ob ein Fehler geworfen wurde oder nicht - Code ausgeführt wird.
const myPromise = new Promise((resolve, reject) => {
resolve();
});
myPromise
.then(() => {
console.log('Das ging mal gut!');
})
.catch(() => {
console.log('Das war diesmal NICHT erfolgreich!');
})
.finally(() => {
console.log('Ich geschehe immer!');
});
Parameter in .catch
und .then
Wir erhalten in diesen beiden Funktionen Informationen.
.catch
erhält in der Regel den Error der davor geworfen wurde, oder aber was in die Funktion reject
mitgegeben wurde.
const p = new Promise((_, reject) => {
reject('Zum Scheitern verdonnert');
});
p.catch((reason) => {
console.log(reason);
throw new Error('Für immer');
})
.catch((err) => {
console.log(err);
});
Das gleiche Funktioniert auch mit resolve()
und .then()
const p = new Promise((resolve) => {
resolve(42);
});
p.then((zahl) => {
console.log(zahl);
return "Die einzig wahre Zahl";
})
.then((x) => {
console.log(x);
});
Ein Konkretes Beispiel
Nachfolgend lesen wir den Inhalt aus dem System-Clipboard aus. Die Funktionalität, die uns der Browser dafür gibt, liefert einen Promise.
const into = document.getElementById('clipboard-result');
document.getElementById('clipboard-paste').onclick = function () {
navigator.clipboard.readText()
.then((clipboardText) => {
into.innerText = `Kopierter Text:\n${clipboardText}`;
})
.catch((err) => {
into.innerText = `Fehler beim lesen des Clipboards: ${err}`;
});
}