Das Beispiel
Das folgende Beispiel ist absichtlich ähnlich denen, wie man sie im Internet findet, wenn
man nach
Promise Example
sucht. Dabei war ein
Beispiel von freeCodeCamp
Inspiration.
HTML
<button onclick="location.reload()">Seite neu laden</button>
<h3>Promise Example</h3>
<h4>Display:</h4>
<div id="display"></div>
CSS
h3, h4 {
margin-top:4pt;
margin-bottom:4pt;
}
#display {
padding:4pt;
border: 1pt solid red;
width: min-content;
white-space: nowrap;
}
JavaScript
// Definitions
const Display = document.getElementById("display");
function Log(parm) { Display.innerHTML += 'Ok : ' + parm + '<br>'; }
function Err(parm) { Display.innerHTML += 'Err: ' + parm + '<br>'; }
const mypromise = new Promise((resolve, reject) => {
const num = Math.random();
if (num >= 0.5) {
resolve(num);
} else {
reject(num);
}
});
// Main
Log('Anfang');
mypromise.then(Log, Err);
Log('Ende');
Wenn wir
dieses Beipiel starten, kann sein,
dass es auf dem Monitor ähnlich so aussieht:
Screenshot 1

oder ähnlich so:
Screenshot 2
Erklärung zum Programmablauf
Im HTML-Bereich gibt es ein div
mit dem class-Attribut "display"
.
Die Funktion Log()
erwartet ein Argument und gibt es mit dem Vorspann Ok:
aus.
Nach allen Definitionen im Scriptcode wird Log() mit dem String 'Anfang'
als erste Anweisung
aufgerufen und folglich erscheint im Display als erstes diese Ausgabe.
Als
letzte Anweisung wird Log() mit
'Ende'
aufgerufen, und auch das sehen wir im Display.
Zwischen beiden Aufrufen steht im Scriptcode ein Promise-Aufruf:
promise.then(Log, Err);
Dieser Code produziert eine Zeile im Display mit einer Gleitkommazahl.
Eigentlich müsste zuerst die Zeile mit Anfang
,
dann die mit der Gleitkommazahl und zuletzt die Zeile mit Ende
ausgegeben werden.
Aber unsere Screenshots zeigen eine andere Reihenfolge - was geht hier ab?
Das Promise startet seine Ausgabe eben in einem anderen Thread.
Das JavaScript-Programm arbeitet so wie erwartet:
- erste Ausgabe ausführen
- das Promise starten
- letzte Ausgabe ausführen - fertig und Schluss
Das geht sehr schnell. Das Promise jedoch arbeitet sein Programm in einem eigenen Tread ab,
also asynchron, will heißen zeitlich parallel und unabhängig vom Hauptprogramm.
Die Ausführung braucht hier ein paar Nanosekunden länger,
daher kommt die Ausgabe des Promise verspätet
an.
Das Promise ist also gedacht, dass es Prozesse abarbeiten soll,
auf die das eigentliche JavaScript-Programm nicht warten soll; etwas das Nachladen eines Fotos oder Videos von einem Server.
Das Promise kann bei Erfolg den passenden Code starten oder im Fehlerfall z.B. eine entsprechende Nachricht ausgeben.
Schauen wir uns nun den Code an, der im Promise steht.
Es wird eine zufällige Zahl
num
von
Math.random()
initialisiert.
Das ist irgendeine Gleitkommazahl zwischen
0
und
1
.
Ist diese Zahl größer oder gleich
0,5
dann gilt dies als
erfolgreich abgearbeitet
,
und im Display steht vor der Zahl das
Ok:
(siehe Screenshot 1).
Ansonsten wird
fehlerhaft ausgeführt
vermutet und vor der Zahl steht
Err:
(Screenshot 2).
Bei fehlerhaft
geht es hier nicht um ein fehlerhaft abgelaufenes Programm, sondern um das Ergebnis
des beabsichtigten Prozesses. Nicht das Promise stellt fest, ob Erfolg oder nicht, sondern der Prozess
selbst muss testen, ob das Ergebnis ein gewünschtes ist oder nicht.
Unser Prozess ist im Kern Math.random()
, und mit Erfolg
haben wir ziemlich willkürlich nur
Resultate eingestuft, die nicht kleiner als 0,5 sind.
Das Promise hat durchaus nichts dagegen, so etwas zu programmieren.
Wenn wir das Beispielprogramm starten, kann entweder das eine oder andere passieren.
Startet man die Seite mehrmals neu (Reload-Button klicken), dann wird mal der eine,
mal der andere Fall eintreten.
Soweit die Beschreibung, was hier passiert.
Vielleicht bleibt ein Gefühl zurück, das man irgendwas noch nicht ganz verstanden hat.
Daher betrachten wir das Beispiel mal ganz genau.
Definition des Promise
const mypromise = new Promise(...);
In dieser Zeile wird ein Promise-Objekt mit Namen mypromise
erzeugt (instanziert).
Der Konstruktoraufruf erwartet in den Klammern ein Argument,
das hier symbolisch mit ... angegeben ist.
Den Code zwischen diesen Klammern betrachten wir mal näher:
(resolve, reject) => {
const num = Math.random();
if (num >= 0.5) {
resolve(num);
} else {
reject(num);
}
}
Es ist eine Funktionsdefinition, das Promise erwartet als Parameter also eine Callbackfunktion.
Die Funktionsdefinition liegt in Pfeilschreibweise vor (
arrow function). Der Code wird deutlicher,
wenn wir die Funktion konservativ umschreiben; also
- mit
function
am Anfang,
- gefolgt von einem Namen (z.B.
myfunction
),
- Parameterliste,
- und Funktionskörper in geschweiften Klammern.
Die Instanzierung unseres Promise verkürzt sich dabei erheblich, statt der ganzen Funktionsdefinition
brauchen wir nur den Funktionsnamen als Parameter anzugeben.
Das Ganze sieht dann so aus:
function myfunction(resolve, reject) {
const num = Math.random();
if (num >= 0.5) {
resolve(num);
} else {
reject(num);
}
}
const mypromise = new Promise(myfunction);
Hier das geänderte Beispiel,
es funktioniert in gleicher Weise.
Natürlich kann die Funktion auch als anonyme Funktion (
closure) geschrieben werden.
let myfunction = function(resolve, reject) {
const num = Math.random();
if (num >= 0.5) {
resolve(num);
} else {
reject(num);
}
}
const mypromise = new Promise(myfunction);
Hier das dritte Beispiel.
Jetzt haben wir die Callbackfunktion extra vorliegen.
Hier packen wir also den Code hinein, den das Promise asynchron ausführen soll.
Wenn es ein sehr umfangreicheres Programm sein sollte, dann ist die Extra-Schreibweise
bestimmt übersichtlicher.
Die Parameter resolve und reject
Die
Verwendung der Parameter
resolve
und
reject
zeigt,
dass es offenbar Funktionsaufrufe sind; hier der betreffende Teilcode:
...
if (num >= 0.5) {
resolve(num);
} else {
reject(num);
}
...
Daraus folgt, dass die Argumente für diese Parameter auch Callbackfunktionen sein müssen.
In unserem Beispiel haben wir dafür die Funktionen
Log()
und
Err()
bereitgestellt.
Im Erfolgsfall ruft das Promise die erste Funktion auf, andernfalls die zweite. Diese Reihenfolge ist
vorgegeben, die Namen der Callbackfunktionen jedoch nicht, die können wir frei wählen.
Vergleiche dazu mit
Beispielen im MDN zu Promise.
Aber wo genau müssen wir die Funktionen angeben? Das geschieht über die Methode
then()
des Promises,
in unserem Beispiel ist es diese Codezeile:
...
mypromise.then(Log, Err);
...
Es geht auch ohne
Wir könnten ein Promise mit einem Code instanzieren, bei denen der Fehlerfall nicht interessiert
oder nicht definiert ist.
Dann brauchen wir keine Callbackfunktion für den Fehlerfall, und können sie einfach weglassen,
im Beispiel so:
...
mypromise.then(Log);
// oder so:
mypromise.then(Log, null);
...
Unser Beispiel zeigt dann nur den Erfolgsfall an.
Andernfalls könnte es sein, dass uns nur der Fehlerfall interessiert.
Dann müssen wir beachten, dass die Callbackfunktion für den Fehlerfall als
zweites Argument
anzugeben ist, und zwar so:
...
mypromise.then(null, Err);
...
Jetzt zeigt unser Beispiel nur den Fehlerfall an.
Zusammenfassung
- Das Promise hat die Aufgabe, einen definierten Code als Prozess in einem eigenen Thread ablaufen zu lassen.
Hauptprogramm und Thread laufen zeitlich parallel, also unabhängig voneinander. In unserem Beispiel ist das Hauptprogramm
eher beendet als der Thread.
- Das Promise ist nicht dafür da, um syntaktische oder Runtime-Fehler abzufangen.
Der Prozess im Thread muss selbst wissen
, ob sich bei Abarbeitung ein Erfolg oder Misserfolg einstellt.
- Was in dem einen oder anderen Fall zu tun ist, entscheidet das Promise nicht.
Es startet nur von außen
bereitgestellte Callback-Funktionen für den einen oder anderen Fall.
Promise erzeugt Promise
Die Promise-Instanz, im Beispiel mypromise
,
hat auch einen Rückgabewert, und zwar: ein neues Promise!
Das können wir feststellen, wenn wir den Rückgabewert abfangen und mit alert() anzeigen lassen.
Code, Screenshot, Link zu Beispiel
Welchen Prozess das neue Promise enthält, wissen wir nicht, denn wir haben es ja selbst nicht instanziert.
Aber Promise bleibt Promise, also gibt es auch hier die Methode
then()
, der wir einfach mal
Callbackfunktionen für die beiden Fälle übergeben.
Zwar können wir hier auch
Log()
und
Err()
verwenden, zur Unterscheidung der Ausgaben
definieren wird die ähnlichen Funktionen
Log2()
und
Err2()
.
vollständiger Teilcode, Screenshot, Link zu Beispiel