Get to know MDN better
Dieser Inhalt wurde automatisch aus dem Englischen übersetzt, und kann Fehler enthalten. Erfahre mehr über dieses Experiment.
Ein Promise ist ein Objekt, das den endgültigen Abschluss oder das Scheitern einer asynchronen Operation darstellt. Da die meisten Menschen Konsumenten von bereits erstellten Promises sind, erklärt dieser Leitfaden zunächst die Verwendung zurückgegebener Promises, bevor er erklärt, wie man sie erstellt.
Im Wesentlichen ist ein Promise ein zurückgegebenes Objekt, an das man Callbacks anhängt, anstatt Callbacks in eine Funktion zu übergeben. Stellen Sie sich eine Funktion createAudioFileAsync() vor, die asynchron eine Audiodatei erstellt, gegeben eine Konfigurationsdatensatz und zwei Callback-Funktionen: eine, die aufgerufen wird, wenn die Audiodatei erfolgreich erstellt wird, und eine andere, die aufgerufen wird, wenn ein Fehler auftritt.
Hier ist ein Code, der createAudioFileAsync() verwendet:
Wenn createAudioFileAsync() umgeschrieben wäre, um ein Promise zurückzugeben, würden Sie Ihre Callbacks daran anhängen:
Diese Konvention hat mehrere Vorteile. Wir werden jeden davon untersuchen.
Ein häufiges Bedürfnis ist es, zwei oder mehr asynchrone Operationen hintereinander auszuführen, wobei jede nachfolgende Operation startet, wenn die vorherige erfolgreich ist, mit dem Ergebnis aus dem vorherigen Schritt. In der Vergangenheit führte die Durchführung mehrerer asynchroner Operationen hintereinander zum klassischen Callback-Hell:
Mit Promises erreichen wir dies, indem wir eine Promise-Kette erstellen. Das API-Design von Promises macht dies großartig, weil die Callbacks an das zurückgegebene Promise-Objekt angehängt werden, anstatt in eine Funktion übergeben zu werden.
Hier ist das Besondere: Die Funktion then() gibt ein neues Promise zurück, das sich vom ursprünglichen unterscheidet:
Dieses zweite Promise (promise2) repräsentiert den Abschluss nicht nur von doSomething(), sondern auch von dem an successCallback oder failureCallback übergebenen Callback — welche andere asynchrone Funktionen sein können, die ein Promise zurückgeben. Wenn dies der Fall ist, werden alle an promise2 hinzugefügten Callbacks hinter das Promise in die Warteschlange eingereiht, das von entweder successCallback oder failureCallback zurückgegeben wird.
Hinweis: Wenn Sie ein funktionierendes Beispiel verwenden möchten, können Sie die folgende Vorlage verwenden, um eine Funktion zu erstellen, die ein Promise zurückgibt:
Die Implementierung wird im Abschnitt Erstellen eines Promises um eine alte Callback-API unten besprochen.
Mit diesem Muster können Sie längere Verarbeitungsreihen erstellen, bei denen jedes Promise den Abschluss eines asynchronen Schrittes in der Kette darstellt. Darüber hinaus sind die Argumente zu then optional, und catch(failureCallback) ist kurz für then(null, failureCallback) — wenn also Ihr Fehlerbehandlungscode für alle Schritte gleich ist, können Sie ihn am Ende der Kette anhängen:
Sie könnten dies mit Pfeilfunktionen ausgedrückt sehen:
Hinweis: Pfeilfunktionsausdrücke können eine implizite Rückgabe haben; also ist () => x kurz für () => { return x; }.
doSomethingElse und doThirdThing können jeden Wert zurückgeben — wenn sie Promises zurückgeben, wird das Promise zuerst gewartet, bis es sich erfüllt, und der nächste Callback erhält den Erfüllungswert, nicht das Promise selbst. Es ist wichtig, immer Promises von then-Callbacks zurückzugeben, selbst wenn das Promise immer undefined auflöst. Wenn der vorherige Handler ein Promise gestartet hat, es aber nicht zurückgegeben hat, gibt es keine Möglichkeit, seine Behebung weiter zu verfolgen, und das Promise wird als "schwebend" angesehen.
Indem wir das Ergebnis des fetch-Aufrufs (ein Promise) zurückgeben, können wir sowohl dessen Abschluss verfolgen als auch dessen Wert erhalten, wenn er abgeschlossen ist.
Schwebende Promises könnten schlimmer sein, wenn Sie Rennbedingungen haben — wenn das Promise vom letzten Handler nicht zurückgegeben wird, wird der nächste then-Handler frühzeitig aufgerufen, und jeder Wert, den er liest, könnte unvollständig sein.
Daher, als Daumenregel, wann immer Ihre Operation auf ein Promise trifft, geben Sie es zurück und überlassen Sie seine Behandlung dem nächsten then-Handler.
Noch besser ist es, die geschachtelte Kette in eine einzige Kette zu glätten, was einfacher ist und die Fehlerbehandlung erleichtert. Die Details werden im Abschnitt Schachtelung unten besprochen.
Die Verwendung von async/await kann Ihnen helfen, Code zu schreiben, der intuitiver ist und synchronem Code ähnelt. Unten ist dasselbe Beispiel mit async/await:
Beachten Sie, wie der Code genau wie synchroner Code aussieht, außer für die await-Schlüsselwörter vor Promises. Eines der wenigen Kompromisse ist, dass es leicht sein kann, das await-Schlüsselwort zu vergessen, das nur behoben werden kann, wenn ein Typ-Mismatch auftritt (z. B. der Versuch, ein Promise als Wert zu verwenden).
async/await baut auf Promises auf — zum Beispiel ist doSomething() dieselbe Funktion wie zuvor, sodass nur minimales Refactoring erforderlich ist, um von Promises zu async/await zu wechseln. Sie können mehr über die async/await-Syntax in den Referenzen zu asynchronen Funktionen und await lesen.
Hinweis: async/await hat die gleichen Nebenläufigkeitssemantiken wie normale Promise-Ketten. await innerhalb einer asynchronen Funktion stoppt nicht das gesamte Programm, sondern nur die Teile, die von seinem Wert abhängen, sodass andere asynchrone Jobs noch ausgeführt werden können, während await aussteht.
Sie erinnern sich vielleicht, dass failureCallback dreimal in der Pyramide des Schreckens vorgekommen ist, verglichen mit nur einmal am Ende der Promise-Kette:
Wenn eine Ausnahme auftritt, sucht der Browser die Kette nach .catch()-Handlern oder onRejected ab. Dies ist stark nach dem Modell, wie synchroner Code funktioniert:
Diese Symmetrie mit asynchronem Code gipfelt in der async/await-Syntax:
Promises lösen einen grundlegenden Fehler mit der Callback-Pyramide des Schreckens, indem sie alle Fehler abfangen, sogar geworfene Ausnahmen und Programmierfehler. Dies ist essentiell für die funktionale Komposition asynchroner Operationen. Alle Fehler werden jetzt von der catch()-Methode am Ende der Kette behandelt, und man sollte fast nie try/catch verwenden müssen, ohne async/await zu verwenden.
In den obigen Beispielen mit listOfIngredients hat das erste einen in den Rückgabewert eines anderen then()-Handlers geschachtelten Promise-Strang, während das zweite eine völlig flache Kette verwendet. Einfache Promise-Ketten sollten flach gehalten werden, ohne Schachtelung, da Schachtelung das Ergebnis unvorsichtiger Komposition sein kann.
Schachtelung ist eine Kontrollstruktur, um den Geltungsbereich von catch-Anweisungen zu begrenzen. Konkret fängt ein geschachteltes catch nur Fehler in seinem Geltungsbereich und darunter ab, nicht aber Fehler, die höher in der Kette außerhalb des geschachtelten Bereichs liegen. Wenn dies richtig verwendet wird, bietet es eine größere Präzision bei der Fehlerbehebung:
Beachten Sie, dass die optionalen Schritte hier geschachtelt sind — die Schachtelung wird nicht durch die Einrückung, sondern durch die Platzierung der äußeren ( und ) Klammern um die Schritte herum verursacht.
Der innere Fehlerunterdrückung-catch-Handler fängt nur Fehler von doSomethingOptional() und doSomethingExtraNice() ab, danach wird der Code mit moreCriticalStuff() fortgesetzt. Wichtig ist, dass, wenn doSomethingCritical() fehlschlägt, sein Fehler nur vom finalen (äußeren) catch gefangen wird und nicht vom inneren catch-Handler unterdrückt wird.
In async/await sieht dieser Code so aus:
Hinweis: Wenn Sie keine ausgeklügelte Fehlerbehandlung haben, brauchen Sie sehr wahrscheinlich keine geschachtelten then-Handler. Verwenden Sie stattdessen eine flache Kette und setzen Sie die Fehlerbehandlungslogik am Ende.
Es ist möglich, nach einem Fehler, d.h. einem catch zu verketteten, was nützlich ist, um nach einem fehlgeschlagenen Vorgang in der Kette neue Aktionen durchzuführen. Lesen Sie das folgende Beispiel:
Dies gibt den folgenden Text aus:
Do that Do this, no matter what happened beforeHinweis: Der Text "Do this" wird nicht angezeigt, da der "Something failed"-Fehler eine Ablehnung verursacht hat.
In async/await sieht dieser Code so aus:
Wenn ein Promise-Ablehnungsereignis von keinem Handler abgefangen wird, wird es bis zum oberen Ende des Aufrufstapels weitergegeben, und der Host muss es anzeigen. Im Web, wann immer ein Promise abgelehnt wird, wird eines von zwei Ereignissen an den globalen Bereich gesendet (in der Regel ist dies entweder das window oder, wenn es in einem Web Worker verwendet wird, das Worker oder eine andere arbeitnehmerbasierte Schnittstelle). Die beiden Ereignisse sind:
unhandledrejectionWird gesendet, wenn ein Promise abgelehnt wird, aber kein Ablehnungshandler verfügbar ist.
rejectionhandledWird gesendet, wenn ein Handler an ein abgelehntes Promise angehängt wird, das bereits ein unhandledrejection-Ereignis verursacht hat.
In beiden Fällen hat das Ereignis (vom Typ PromiseRejectionEvent) als Mitglieder eine promise-Eigenschaft, die das abgelehnte Promise anzeigt, und eine reason-Eigenschaft, die den Grund angibt, warum das Promise abgelehnt wurde.
Diese ermöglichen es, eine alternative Fehlerbehandlung für Promises anzubieten und helfen dabei, Probleme mit Ihrem Promise-Management zu debuggen. Diese Handler sind global pro Kontext, sodass alle Fehler an dieselben Ereignis-Handler gehen, unabhängig von der Quelle.
In Node.js ist die Behandlung von Promise-Ablehnungen etwas anders. Sie erfassen unbehandelte Ablehnungen, indem Sie einen Handler für das Node.js-unhandledRejection-Ereignis hinzufügen (achten Sie auf die Groß- und Kleinschreibung des Namens), so:
Für Node.js, um zu verhindern, dass der Fehler in die Konsole geloggt wird (die Standardaktion, die sonst erfolgen würde), reicht es aus, diesen process.on()-Listener hinzuzufügen; es besteht keine Notwendigkeit für ein Äquivalent zur preventDefault()-Methode der Browserlaufzeit.
Wenn Sie jedoch diesen process.on-Listener hinzufügen, aber keine Logik darin haben, um abgelehnte Promises zu behandeln, werden sie einfach ignoriert. Idealerweise sollten Sie daher Code innerhalb dieses Listeners hinzufügen, um jedes abgelehnte Promise zu untersuchen und sicherzustellen, dass es nicht durch einen tatsächlichen Programmierfehler verursacht wurde.
Es gibt vier Zusammensetzungstools für die gleichzeitige Ausführung von asynchronen Operationen: Promise.all(), Promise.allSettled(), Promise.any(), und Promise.race().
Wir können Operationen gleichzeitig starten und warten, bis sie alle beendet sind, wie folgt:
Wenn eines der Promises im Array abgelehnt wird, lehnt Promise.all() sofort das zurückgegebene Promise ab. Die anderen Operationen laufen weiter, aber ihre Ergebnisse sind nicht über den Rückgabewert von Promise.all() verfügbar. Dies kann zu unerwartetem Verhalten führen. Promise.allSettled() ist ein weiteres Zusammensetzungstool, das sicherstellt, dass alle Operationen abgeschlossen sind, bevor es aufgelöst wird.
Diese Methoden führen alle Promises gleichzeitig aus — eine Folge von Promises wird gleichzeitig gestartet und wartet nicht aufeinander. Sequenzielle Zusammensetzung ist mit einigem cleveren JavaScript möglich:
In diesem Beispiel reduzieren wir ein Array von asynchronen Funktionen zu einer Promise-Kette. Der obige Code entspricht:
Dies kann zu einer wiederverwendbaren Compose-Funktion gemacht werden, die in der funktionalen Programmierung üblich ist:
Die composeAsync()-Funktion akzeptiert beliebig viele Funktionen als Argumente und gibt eine neue Funktion zurück, die einen Anfangswert akzeptiert, der durch die Kompositionspipeline geleitet wird:
Sequentielle Zusammensetzung kann auch prägnanter mit async/await durchgeführt werden:
Bevor Sie jedoch Promises sequentiell zusammensetzen, überlegen Sie, ob es wirklich notwendig ist — es ist immer besser, Promises gleichzeitig auszuführen, sodass sie sich nicht unnötig blockieren, es sei denn, die Ausführung eines Promises hängt von dem Ergebnis eines anderen ab.
Promise selbst verfügt über kein erstklassiges Protokoll für den Abbruch, aber es ist möglich, die zugrunde liegende asynchrone Operation direkt abzubrechen, typischerweise mit AbortController.
Ein Promise kann von Grund auf neu mit seinem Konstruktor erstellt werden. Dies sollte nur benötigt werden, um alte APIs zu umschließen.
In einer idealen Welt würden alle asynchronen Funktionen bereits Promises zurückgeben. Leider erwarten einige APIs immer noch, dass Erfolgs- und/oder Fehler-Callbacks auf die alte Weise übergeben werden. Das offensichtlichste Beispiel ist die setTimeout()-Funktion:
Das Mischen von Rückrufen im alten Stil und Promises ist problematisch. Wenn saySomething() fehlschlägt oder einen Programmierfehler enthält, fängt nichts dies ab. Dies ist intrinsisch zum Design von setTimeout().
Glücklicherweise können wir setTimeout() in ein Promise verpacken. Die beste Praxis besteht darin, die rückrufannehmenden Funktionen so niedrig wie möglich zu umschließen und sie dann nie wieder direkt aufzurufen:
Der Promise-Konstruktor akzeptiert eine Ausführungsfunktion, die es uns ermöglicht, ein Promise manuell aufzulösen oder abzulehnen. Da setTimeout() nicht wirklich fehlschlägt, haben wir reject in diesem Fall weggelassen. Weitere Informationen darüber, wie die Ausführungsfunktion funktioniert, finden Sie in der Promise()-Referenz.
Zum Schluss schauen wir uns noch die technischen Details an, wann die registrierten Callbacks aufgerufen werden.
In der rückrufbasierten API hängt es vom API-Implementierer ab, wann und wie der Rückruf aufgerufen wird. Zum Beispiel könnte der Rückruf synchron oder asynchron aufgerufen werden:
Das obige Design wird stark abgeraten, da es zum sogenannten "Zalgo-Zustand" führt. Im Kontext des Entwerfens asynchroner APIs bedeutet dies, dass ein Rückruf in einigen Fällen synchron, in anderen Fällen jedoch asynchron aufgerufen wird und so Ambiguitäten für den Anrufer entstehen. Weitere Hintergrundinformationen finden Sie im Artikel Designing APIs for Asynchrony, wo der Begriff erstmals formell vorgestellt wurde. Dieses API-Design macht Nebeneffekte schwer analysierbar:
Auf der anderen Seite sind Promises eine Form der umgekehrten Kontrolle — der API-Implementierer kontrolliert nicht, wann der Rückruf aufgerufen wird. Stattdessen wird die Verwaltung der Rückrufwarteschlange und die Entscheidung, wann die Rückrufe aufgerufen werden, an die Promise-Implementierung delegiert, und sowohl der API-Anwender als auch der API-Entwickler erhalten automatisch starke semantische Garantien, einschließlich:
Um Überraschungen zu vermeiden, werden Funktionen, die an then() übergeben werden, niemals synchron aufgerufen, auch nicht bei einem bereits gelösten Promise:
Anstatt sofort zu laufen, wird die übergebene Funktion in eine Microtask-Warteschlange gestellt, was bedeutet, dass sie später ausgeführt wird (nur nachdem die Funktion, die sie erstellt hat, beendet ist und wenn der JavaScript-Ausführungsstack leer ist), gerade bevor die Kontrolle an die Ereignisschleife zurückgegeben wird; d.h. ziemlich bald:
Promise-Callbacks werden als Microtask behandelt, wohingegen setTimeout()-Callbacks als Aufgabenwarteschlangen behandelt werden.
Der obige Code gibt Folgendes aus:
Promise callback Promise (pending) Promise {<pending>} Promise callback (.then) event-loop cycle: Promise (fulfilled) Promise {<fulfilled>}Weitere Details finden Sie unter Aufgaben vs. Microtasks.
Wenn Sie auf Situationen stoßen, in denen Sie Promises und Aufgaben (wie Ereignisse oder Rückrufe) haben, die in unvorhersehbaren Reihenfolgen ausgelöst werden, ist es möglich, dass Sie von der Verwendung eines Microtasks profitieren könnten, um den Status zu überprüfen oder Ihre Promises auszugleichen, wenn Promises bedingt erstellt werden.
Wenn Sie glauben, dass Microtasks zur Lösung dieses Problems beitragen können, lesen Sie den Microtask-Leitfaden, um mehr darüber zu erfahren, wie Sie queueMicrotask() verwenden, um eine Funktion als Microtask in die Warteschlange einzureihen.
Der Bauplan für ein besseres Internet.
Besuche die gemeinnützige Muttergesellschaft der Mozilla Corporation, die Mozilla Foundation.
Teile dieses Inhalts sind ©1998–2026 von einzelnen mozilla.org-Mitwirkenden. Inhalte sind verfügbar unter einer Creative-Commons-Lizenz.