JavaScript/Objekte/Promise

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Das mit ECMAScript 2015 (ES6) eingeführte Konstruktorfunktion Promise dient zur Repräsentation asynchroner Vorgänge. Der asynchrone Vorgang wird eingeleitet durch eine Exekutor-Funktion, die der Promise-Konstruktor als Parameter erhält. Das Ergebnis ist über Callback-Funktionen abrufbar, die über die then und catch Methoden des Promise-Objekts registriert werden. Für den Einsatz in älteren Browsern steht ein Polyfill zur Verfügung (siehe Weblinks).

  • ECMA 6
  • Chrome
  • Firefox
  • Edge
  • Opera
  • Safari

Details: caniuse.com

Konstruktor

var promise = new Promise(executor);

Methoden

  • all
  • race
  • reject
  • resolve

Methoden von Promise.prototype

  • then
  • catch

Text 1[Bearbeiten]

Es gibt unterschiedliche Möglichkeiten, in JavaScript asynchrone Vorgänge auszulösen. Zum Beispiel durch Requests an einen Server (Ajax, Laden von Ressourcen) oder Web Worker. Um die weitere Arbeit mit diesen Vorgängen zu synchronisieren, können Promises genutzt werden.

Der Einsatz eines Promise verläuft in zwei Schritten.

Im ersten Schritt wird ein Promise erzeugt und dem Konstruktor eine Funktion als Parameter übergeben, der so genannte Exekutor. Dieser Exekutor wird vom Konstruktor sofort aufgerufen und bekommt seinerseits zwei Callbackfunktionen als Parameter übergeben, den Erfüller (resolve) und den Zurückweiser (reject). Was der Exekutor genau tut, ist ihm überlassen, ein Promise ist aber nur dann sinnvoll, wenn er irgendeine Form von asynchroner Aktivität startet und dabei so vorgeht, dass dabei sichergestellt wird, dass nach Abschluss dieser Aktivität der Erfüller aufgerufen wird und im Fall eines Fehlers der Zurückweiser.

Syntax

new Promise(function(resolve, reject) { ... });

Dieses Promise kann nun als Lieferant für das irgendwann in der Zukunft verfügbare Ergebnis der asynchron gestarteten Aktivität verwendet werden. Solange der Exekutor nicht den Erfüller oder Zurückweiser aufgerufen hat, ist das Promise in einem Schwebezustand (pending). Nachdem einer der beiden aufgerufen wurde, ist das Promise festgelegt (settled) und zwar entweder auf erfüllt (fulfilled) oder zurückgewiesen (rejected).

Erfüller und Zurückweiser erwarten jeweils einen Parameter. Der Erfüller den Erfüllungswert des Promise, der Zurückweiser den Zurückweisungsgrund. Diese Werte werden vom Promise später zur Verfügung gestellt.

Das weitere Verhalten des Promise hängt davon ab, in welchem Zustand es ist. Der Nutzer des Promise verwendet zwei Methoden, die auf Promise.prototype definiert sind und die Erfüllungs- bzw. Zurückweisungs-Callbackfunktionen als Parameter erwarten:

  • then(onFulfill, onReject)
  • catch(onReject)

Die catch-Methode ist eine Kurzform von then(null, onReject). Solange das Promise im Status pending ist, erstellen beide Funktionen lediglich einen Warteschlangeneintrag im Promise. In dem Moment, wo der Exekutor einen seiner beiden Callbacks aufruft, wird der Status des Promise auf fulfilled oder rejected geändert und dann die zugehörige Callback-Warteschlange abgearbeitet. Dabei bekommen die onFullfill-Funktionen den Erfüllungswert des Promise als Parameter, und die onReject-Funktionen den Zurückweisungsgrund.

Wird then oder catch aufgerufen, wenn das Promise bereits settled ist, wird die entsprechende Callbackfunktion direkt aufgerufen.

Genauere Informationen zu then und catch, unter anderem zu ihren Parametern und der Interaktion mit den Erfüller- und Zurückweiserfunktionen, werden Sie auf den entsprechenden Wiki-Seiten finden, sobald sie erstellt sind.

Eine Besonderheit von then und catch ist, dass sie ihrerseits ein Promise zurückgeben. Der Übersicht halber soll das zuvor erzeugte Promise als das ursprüngliche Promise bezeichnet werden, und dieses neue Promise als das "äußere Promise" (denn gleich folgt noch ein inneres). Das äußere Promise ist solange im Status pending, bis die Callbacks von then bzw. catch ausgeführt wurden. Wie es weitergeht, hängt vom Rückgabewert des Callbacks ab:

  • Ist es ein Wert (auch undefined), der kein Promise ist, wird das äußere Promise auf fulfilled gesetzt und der zurückgegebene Wert wird zum Erfüllungswert. Wichtig: Das gilt auch für den Zurückweisungscallback. Das ursprüngliche Promise kann rejected sein, aber wenn der Zurückweisungscallback beispielsweise 'true' zurückgibt, gilt das äußere Promise als fulfilled.
  • Wirft der Callback eine Exception, gilt das äußere Promise als rejected. Das geworfene Exceptionobjekt wird als Zurückweisungsgrund gesetzt.
  • Wird vom Callback ein Promise zurückgegeben (das innere Promise), so wird der Status des äußeren Promise mit dem Status des inneren Promise gekoppelt. Sobald das innere Promise von pending auf einen settled-Zustand wechselt, wechselt das äußere Promise mit.

Das bedeutet, dass man Promise-Ketten bilden kann. Angenommen, die Funktionen doSomethingWithPromise und promiseMeSomethingMore lösen beide asynchrone Aktivitäten aus und liefern ein Promise dazu zurück. Dann kann man so erreichen, dass beide Aktivitäten nacheinander ablaufen und danach eine Abschlussfunktion gerufen wird:

doSomethingWithPromise()
.then(promiseMeSomethingMore)
.then(completeTheTask)

Wichtig bei then ist noch, dass eine Exception im Erfüllungscallback nicht den Zurückweisungscallback des gleichen then-Aufrufs auslöst. Will man das erreichen, muss man den Umstand nutzen, dass eine Exception im Erfüllungscallback das von then zurückgegebene Promise auf rejected setzt. D.h. mit diesem Konstrukt

doSomethingWithPromise()
.then(receiveResult)
.catch(handleFailure)

kann man in der handleFailure-Funktion sowohl ein zurückgewiesenes Promise als auch eine Exception in receiveResult behandeln.

Erzeugen eines Promise[Bearbeiten]

Oft werden Promises nicht von Ihnen erzeugt. Viele JavaScript Bibliotheken geben Promises zurück, um Ihnen die Synchronisierung mit asynchronen Vorgängen zu ermöglichen, und neuere APIs wie z.B. Service Worker ebenfalls.

Wenn Sie ältere APIs in Promises kapseln wollen, erzeugen Sie das benötigte Promise-Objekt selbst. Dazu schreiben Sie eine Funktion, die die gewünschte asynchrone Operation auslöst und übergeben diese Funktion als Argument an den Promise-Konstruktor. Der Konstruktor führt diese Funktion sofort aus und übergibt Callbacks-Funktionen resolve und reject. Genau eine dieser beiden Callback-Funktionen müssen Sie irgendwann aufrufen, damit das Promise aus dem Schwebezustand pending herauskommt.

var executor = function(resolve, reject) {
   // ...
}

var promise = new Promise(executor);

In der Exekutor-Funktion müssen Sie die Rückmeldungen des ausgelösten asynchronen Vorgangs wie gewohnt entgegennehmen. Sie haben, wenn die Rückmeldung eintrifft, nun genau eine Reaktionsmöglichkeit. Ist der Vorgang erfolgreich gewesen, rufen Sie resolve(ergebnis) auf. Ist er fehlgeschlagen, rufen Sie reject(fehlergrund) auf. Was das Ergebnis oder der Fehlergrund nun genau ist, bleibt Ihnen überlassen. Das Promise stellt diese Werte unverändert als Ergebnis des Promise bereit. Dazu gleich mehr.

Zustände und Verhalten[Bearbeiten]

Ein Promise ist in einem von drei grundlegenden Zuständen:

  • pending - Schwebezustand, der asynchrone Vorgang ist noch nicht beendet
  • fulfilled - Erfüllt, der asynchrone Vorgang war erfolgreich
  • rejected - Zurückgewiesen, der asynchrone Vorgang ist gescheitert

Die Zustände fulfilled und rejected werden auch unter dem Oberbegriff settled zusammengefasst.

Zustandswechsel sind nur von pending nach fulfilled oder von pending nach rejected möglich. Sobald ein Promise settled ist, werden weitere Aufrufe der resolve oder reject Callbacks ignoriert. Darüber hinaus kann ein Promise auch gesperrt werden. Es erhält seinen Wert dann indirekt durch ein anderes Promise. Eine solche Sperrung erfolgt unter bestimmten Bedingungen implizit, eine Methode wie lock gibt es nicht.

Um den Wert, mit dem ein Promise erfüllt oder zurückgewiesen wird, abzuholen, stellt Promise.prototype zwei Methoden bereit: then und catch. Catch dient der Lesbarkeit und Bequemlichkeit, folgende Aufrufe bewirken das Gleiche:

promise.catch(rejectHandler);
promise.then(null, rejectHandler);

Die then Methode erwartet zwei Parameter, deren Argumente Funktionen oder null sein müssen: einen fulfillHandler und einen rejectHandler. Die fulfillHandler werden für erfüllte Promises aufgerufen und bekommen den Erfüllungswert des Promise als Parameter übergeben. Analog werden die rejectHandler für zurückgewiesene Promises aufgerufen und bekommen den Zurückweisungsgrund übergeben.

Die übergebenen Handler werden nie synchron aufgerufen. Statt dessen definiert ES6 eine PromiseJob Queue, in die die Handler als PromiseJob eingestellt werden. Die Queue wird abgearbeitet, wenn das aktuell laufende Skript endet.

Eine andere Frage ist, wann die PromiseJobs in die Queue gelangen. Dazu sind zwei Fälle zu unterscheiden.

then und catch bei einem pending Promise

Solange das Promise auf pending steht, gelangen die fulfill- und rejectHandler nicht in die PromiseJob Queue. Statt dessen speichert das Promise sie beim Aufruf von then oder catch in internen Listen zwischen. Wird null statt einer Funktion übergeben, wird nichts gespeichert.

then und catch bei einem settled Promise

Ist das Promise nicht mehr im pending Status, findet keine Zwischenspeicherung mehr statt. Die erforderlichen PromiseQueue Jobs werden sofort erstellt.

Gemeinsame Verhaltensweise

In beiden Fällen geben then und catch ein neues Promise P2 zurück. P2 hat keine Exekutor-Funktion, sondern ist gesperrt und zunächst im pending Zustand. Der PromiseJob, der zuvor in die PromiseJob Queue eingestellt wurden, kennt dieses Promise. Sobald der Job gelaufen ist, wird der Rückgabewert der Handler-Funktion ausgewertet und davon abhängig P2 auf resolved oder rejected gesetzt.

  • Ist die Handler-Funktion null, so wird der Zustand des ursprünglichen Promise auf P2 übertragen.
  • Gibt die Handler-Funktion ein weiteres Promise P3 zurück, so wird P2 an P3 gebunden, d. h. der Zustand von P2 folgt dem von P3.
  • Gibt die Handler-Funktion irgendeinen anderen Wert zurück, so wird P2 erfüllt und der zurückgegebene Wert als sein Erfüllungswert gesetzt.
  • Wirft die Handler-Funktion eine Exception, so wird P2 zurückgewiesen und die Exception wird zum Zurückweisungsgrund.

Weblinks[Bearbeiten]

deutsch: