• aktuell
  • über mich
  • mein blog
  • impressum

Promise 🤞

JavaScript ist Single-Threaded , was bedeutet, dass nicht zwei Skriptbits gleichzeitig ausgeführt werden können. Deshalb werden sie nacheinander ausgeführt. In Browsern teilt JavaScript einen Thread mit einer Menge anderer Dinge, was sich natürlich von Browser zu Browser unterschiedlich verhalten kann. In der Regel befindet sich JavaScript jedoch in derselben Queue (Warteschlange) wie das Aktualisieren von Styles und das Handling von Benutzeraktionen (z. B. Hervorheben von Text und Interaktion mit Formelementen). So kommt es unter Umständen zu einer langen Warteschlange.

Als Mensch ist man Multi-Threaded. Wir können mit mehreren Fingern tippen und gleichzeitig ein Gespräch führen. Beim Niesen allerdings werden alle aktuellen Aktivitäten für die Dauer des Niesens ausgesetzt. Das ist ziemlich ärgerlich und unter Umständen sogar gefährlich, besonders wenn man im Auto hinter dem Steuer sitzt und sich auf die Strasse konzentriert. Kein Code sollte also niesen.

Promises selbst sind nichts anderes als Objekte, die sozusagen als Platzhalter für das Ergebnis einer asynchronen Funktion dienen.

  • Pending: der initiale State - der Prozess (GET/POST) ist noch nicht am Ende.
  • Fulfilled: die Operation ist erfolgreich abgeschlossen und der Promise hat einen resolved value. Beispielsweise wird ein Promise mit einem JSON {} erfolgreich als value vollendet.
  • Rejected: die Operation ist fehlgeschlagen. Dabei wird die Ursache des Errors angegeben.

Man spricht von einem settled -Promise, wenn er sich nicht mehr im pending befindet, d.h., dass er entweder fulfilled oder rejected wurde.

Geschirrspüler 🍽️

  • Pending der Geschirrspüler läuft, hat aber den Spülprozess noch nicht beendet.
  • Fulfilled der Geschirrspüler hat den Waschvorgang beendet und ist nun gefüllt mit sauberem Geschirr.
  • Rejected der Geschirrspüler hat ein Problem. Die Kammer mit dem Tab wurde nicht verwendet. Das Geschirr ist noch dreckig.

Ist das Geschirrwaschen erfolgreich gewesen, kann man zum nächsten Schritt übergehen. Das Ausräumen kann nun beginnen. Im Falle Rejected kann man versuchen, den Waschvorgang zu wiederholen oder im worst case mit der Hand waschen.

Alle Promises ermöglichen dem Entwickler Logik für den fulfilled - als auch den rejected- Fall zu implementieren.

const geschirrspueler = document.querySelector('geschirrspueler');

geschirrspueler.addEventListener('load', () => {
		// Waschvorgang erfolgreich beendet
	}
)

geschirrspueler.addEventListener('error', () => {
		// Geschirr ist immer noch dreckig
	}
)

Achtung ⚠️: In diesem Beispiel, kann es unter Umständen beim laden von images dazu kommen, dass die Events vor dem listening passieren. Workarounds wären dann mit der propertycomplete als if-Abfrage möglich:

function geladen() {
	// Bild wurde geladen
}

if (image.complete) {
	geladen();
} else {
image.addEventListener('load', geladen)
};

image.addEventListener('error', () => {
	// Fehler
	}
);

Versprochen ist Versprochen

Ein Promise Objekt wird mithilfe vom Keyword new und dem Promise constructor erzeugt:

const executorFunction = (resolve, reject) => { };
const myFirstPromise = new Promise(executorFunction);

Der Promise - Konstruktor bekommt einen function parameter ➡️ executor function. Dieser function parameter wird ausgeführt, wenn der Konstruktor aufgerufen wurde. Der executor function startet i.d.R. eine asynchrone Operation und gibt vor wie der Promise aufgefangen werden soll.

Der executor function besitzt zwei function parameter, die normalerweise in resolve() und reject() aufgelöst sind. Diese zwei Parameter werden nicht vom Entwickler definiert. Wird der Promise - Konstruktor aufgerufen, übergibt Javascript seinen eigenen resolve() und reject() - Funktionen an die executor function:

  • resolve nimmt nur ein Argument. Ein Blick unter die Haube zeigt, dass beim Aufrufen sich der Status des Promise von pending (ausstehend) in fulfilled (erfüllt) auflöst. Der value wird dann an resolve() übergeben.
  • reject nimmt ebenfalls nur ein Argument, wobei dieses ein reason oder error sein kann. Auch hier zeigt der Blick unter die Haube, dass beim Aufrufen sich der Status ändert und als Argument in reject() übergeben wird.

Schauen wir uns ein Beispiel zum Promise - Konstruktor an:

// false manuell setzen für reject
const meineBedingung = true;
const executorFunction = (resolve, reject) => {
  if (meineBedingung) {
      resolve('I resolved!');
  } else {
      reject('I rejected!'); 
  }
}
const meineErsterPromise = new Promise(executorFunction);
  • wir deklarieren eine Variable meineErsterPromise
  • meineErsterPromise - Konstruktor wird mit new Promise() aufgerufen
  • exexutorFunction() wird an den Konstruktor übergeben und hat zwei function parameter: resolve und reject
  • Wenn die Bedingung meineBedingung wahr/true ist, dann wird resolve() aufgerufen ➡️ Ergebnis: 'I resolved!'
  • Wenn die Bedingung meineBedingung falsch/false, dann wird rejected() aufgerufen ➡️ Ergebnis: 'I rejected!'

Das ist ein ziemlich simples Beispiel. Einfach eine Bedingung, die True oder False zurückgibt und dann den Promise auflöst ist ziemlich banal. In der gängigen Praxis resultieren Promises aus asynchronen Abfragen. Beispielsweise können Daten asynchron aus einer Datenbank abgefragt werden und das Ergebnis in einem Promise je nach Ergebnis aufgelöst werden.

⚠️ Hier geht es nur um die Grundzüge von Promises. Fortsetzung folgt…

Quellen

  • Ackermann, Philip (2018), Professionell entwickeln mit JavaScript: Design, Patterns, Praxistipps

Bünyamin Tunc

Wednesday, Feb 10, 2021