November 2024
Wir beißen uns gar nicht an dem Wort Promise (übersetzt: Versprechen
) fest
und sagen gleich, worum es geht;
nämlich um das Starten eines Teilprogramms in einem eigenen Thread.
Wir betrachten gleich mal den Code des folgenden Beispiels und schauen uns an, was dabei passiert.
Das Beispiel
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 und das Beispiel reagiert genauso - 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 aber arbeitet sein Programm in einem eigenen Tread ab,
also zeitlich parallel und unabhängig vom Hauptprogramm. Es braucht halt ein paar Nanosekunden länger,
daher kommt die Ausgabe des Promise verspätet
.
Das Promise ist also gedacht, dass es Prozesse abarbeiten soll, die etwas länger dauern könnten,
auf die aber das 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).
Wenn wir das Beispielprogramm starten, kann entweder das eine oder andere passieren;
Seite über Reload-Button ggf. mehrmals herunterladen.
Bei diesem Programm gibt es keinen Runtime-Fehler, es läuft nicht wirklich fehlerhaft.
Das Programm definiert selbst, was als erfolgreich abgearbeitet
und als fehlerhaft ausgeführt
gilt.
Soweit eine Erklärung, was hier passiert,
aber den Code mit dem Promise verstehen wir wahrscheinlich
noch nicht. Die nächsten Abschnitte klären auf.
Definition des Promise
const mypromise = new Promise(...);
In dieser Zeile wird ein Promise-Objekt mit Namen mypromise
erzeugt.
Der Konstruktoraufruf erwartet in den Klammern ein Argument, das hier symbolisch mit ... angegeben ist.
Genau das Gleiche passiert auch in unserem Beispiel, nur dass zwischen den Klammern ziemlich viel Code steht,
nämlich dieser:
(resolve, reject) => {
const num = Math.random();
if (num >= 0.5) {
resolve(num);
} else {
reject(num);
}
}
Genauer betrachtet erkennt man, dass das eine Funktionsdefinition in Pfeilschreibweise ist.
Konservativ programmiert beginnt eine Funktionsdefinition mit function
gefolgt von einem Funktionsnamen. Schreiben wir den Code doch mal um; als Funktionsnamen
verwenden wir myfunction
.
function myfunction(resolve, reject) {
const num = Math.random();
if (num >= 0.5) {
resolve(num);
} else {
reject(num);
}
}
Und die Instanzierung unseres Promise verkürzt sich erheblich:
const mypromise = new Promise(myfunction);
Programmierer, die von anderen Programmiersprachen kommen, wundern sich wahrscheinlich, dass eine
komplette Funktiondefinition wie im ersten Beispiel als Argument fungieren kann. Das liegt an der
Besonderheit von JavaScript, dass alles ein Objekt ist, auch eine Funktionsdefinition,
myfunction
ist nicht nur der Name einer Funktion, sondern quasi auch ein Objekt,
das eben eine Funktion enthält. Wenn wir unsere Funktion als anomyme Funktion schreiben und einer Variablen zuweisen,
wird das wahrscheinlich deutlicher.
let myfunction = function(resolve, reject) {
const num = Math.random();
if (num >= 0.5) {
resolve(num);
} else {
reject(num);
}
}
Soweit
const mypromise = new Promise(myfunction);
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, die Argumente für diese Parameter müssen auch Callback-Funktionen sein.
In unserem Beispiel haben wir dafür die Funktionen Log()
und Err()
bereitgestellt.
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);
...
Zusammenfassung
- Das Promise hat die Aufgabe, einen definierten Code als Prozess in einem eigenen Thread ablaufen zu lassen.
Hauptprogramm und Thread laufen zeitlich parallel, aber 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.
Ausblick
Damit haben wir die Grundfunktionalität des Promise erklärt, aber noch lange nicht alles gesagt.
Es gibt weitere Eigenschaften und Methoden, die die Anwendung von parallel laufenden Prozessen steuern.
Unser Beispiel beleuchtet den Fall, dass wir einen Prozess bereitgestellt haben, der parallel ablaufen soll.
Es gibt aber auch Funktionen, z.B. fetch()
, die ein Promise als Ergebnis zurückgeben.
Da wissen wir nicht im Detail, was intern passiert und benötigen zusätzliche Informationen, um das Ergebnis zu verwerten.