Moderne Webseiten laden Daten nach, schicken Formulare per Hintergrund-Request und reagieren auf Klicks, ohne die Seite neu zu laden. Hinter all dem steckt asynchrones JavaScript. Ein zentrales Werkzeug dafür sind Promises, also Objekte, die ein Ergebnis in der Zukunft versprechen.
Wer Promises versteht, schreibt stabileren Code, vermeidet Callbacks-Chaos und kann Libraries oder Frameworks besser nutzen. Dieser Artikel führt Schritt für Schritt durch die Grundlagen und zeigt typische Praxisfälle – verständlich, ohne unnötigen Fachnebel.
JavaScript Promises einfach erklärt
Was ist ein Promise in JavaScript?
Ein Promise ist ein JavaScript-Objekt, das den zukünftigen Abschluss einer asynchronen Operation beschreibt. Vereinfacht:
- Jetzt wird etwas angestoßen (z. B. ein HTTP-Request).
- Später kommt entweder ein Erfolg (Daten) oder ein Fehler.
- Der Code definiert vorab, was in beiden Fällen passieren soll.
Ein Promise kennt drei Zustände:
- pending – läuft gerade, Ergebnis noch offen.
- fulfilled – erfolgreich abgeschlossen, es liegt ein Wert vor.
- rejected – fehlgeschlagen, es liegt ein Fehler vor.
Statt verschachtelte Callback-Funktionen zu schreiben, können Reaktionen auf Erfolg und Fehler klar an das Promise-Objekt angehängt werden.
Warum Promises statt klassischer Callbacks?
Früher wurden asynchrone Abläufe oft mit verschachtelten Funktionsaufrufen (Callbacks) gelöst. Das führte zu unübersichtlichem „Callback-Hell“ und schwer nachvollziehbaren Fehlern. Promises lösen mehrere Probleme auf einmal:
- Klare Trennung von Start der Operation und Reaktion auf das Ergebnis.
- Standardisierte Fehlerbehandlung über eine Kette von Operationen.
- Einfaches Kombinieren mehrerer asynchroner Schritte.
Wer bereits mit Funktionen wie fetch(), vielen modernen Browser-APIs oder Libraries arbeitet, hat mit hoher Wahrscheinlichkeit schon mit Promises zu tun – oft ohne es zu wissen.
Promise-Grundlagen mit then, catch und finally
Ein Promise erzeugen und nutzen
Viele Browser-APIs liefern bereits ein Promise zurück. Das einfachste Praxisbeispiel ist ein Netzwerkaufruf mit fetch():
fetch('/api/user') gibt ein Promise zurück, das irgendwann mit der Serverantwort erfüllt oder mit einem Fehler abgelehnt wird.
Der typische Aufbau:
.then()für den Erfolgsfall..catch()für Fehler..finally()für Aufräumarbeiten, die immer laufen sollen.
So bleibt der Ablauf linear lesbar, anstatt in immer tiefere Verschachtelungen zu rutschen. Wer bereits mit strukturierter Fehlerbehandlung im Frontend arbeitet, kann diese Muster mit Promises gut kombinieren.
Promise-Ketten verstehen
Ein großer Vorteil von Promises sind sogenannte Ketten (Chains). Nach einem .then() kann direkt der nächste Schritt folgen. Jeder Schritt gibt entweder einen Wert oder ein weiteres Promise zurück:
- Gibt ein
.then()einen normalen Wert zurück, wird dieser an den nächsten.then()weitergegeben. - Gibt es ein weiteres Promise zurück, wartet die Kette auf dessen Abschluss.
- Tritt in einem Schritt ein Fehler auf (auch ein JavaScript-Fehler), landet er im nächsten
.catch().
So entsteht ein klarer, lesbarer Fluss für mehrere Arbeitsschritte hintereinander, etwa „Daten laden → filtern → anzeigen“.
finally für Aufräumarbeiten nutzen
.finally() wird aufgerufen, sobald die gesamte Promise-Kette abgeschlossen ist – egal ob erfolgreich oder mit Fehler. Typische Einsätze:
- einen Lade-Spinner ausblenden, egal wie der Request ausgegangen ist,
- UI-Elemente wieder aktivieren,
- temporäre Ressourcen freigeben.
Wichtig: .finally() bekommt keinen Wert oder Fehler übergeben. Es ist nur für Aufgaben gedacht, die unabhängig vom Ergebnis sind.
async und await: Promises lesbar machen
async/await – die Kurzform für Promise-Code
Mit async/await lassen sich Promises schreiben, als wären sie synchron – der Code bleibt aber asynchron. Eine Funktion wird mit dem Schlüsselwort async markiert und kann dann mit await auf ein Promise „warten“.
Der Vorteil: Der Ablauf liest sich wie Schritt-für-Schritt-Code. Gleichzeitig bleibt alles nicht-blockierend, weil JavaScript im Hintergrund weiterhin mit Event Loop und Task-Queue arbeitet, wie in Erklärungen zur JavaScript Event Loop beschrieben.
Fehlerbehandlung mit try/catch und async/await
In async-Funktionen lassen sich Fehler klassisch mit try/catch abfangen, anstatt alles über .catch() zu lösen. Das macht den Code besonders für Einsteiger in asynchrones Programmieren leichter verständlich.
Wichtig dabei:
- Nur innerhalb von
async-Funktionen kannawaitverwendet werden. - Auch ein Fehler in einer
async-Funktion erzeugt ein Promise, das abgelehnt wird, wenn der Fehler nicht imtry/catchbehandelt wird.
So lässt sich vertraute Fehlerlogik aus synchronem Code direkt weiter nutzen.
Promise.all, Promise.race & Co. – mehrere Aufgaben koordinieren
Promise.all für parallele Requests
Häufig sollen mehrere Datenquellen gleichzeitig geladen werden, bevor im UI etwas passiert. Promise.all() nimmt ein Array von Promises und wartet, bis alle erfolgreich sind:
- Alle Promises werden parallel gestartet.
- Erst wenn jedes erfüllt ist, geht es weiter.
- Schlägt eines fehl, wird das gesamte Promise abgelehnt.
Das eignet sich etwa für Dashboards, in denen mehrere Widgets gleichzeitig Daten laden, aber erst zusammen angezeigt werden sollen.
Promise.race, allSettled und any im Überblick
Neben Promise.all() gibt es weitere Hilfsfunktionen:
Promise.race()– liefert das erste Promise, das fertig ist, egal ob Erfolg oder Fehler. Praktisch für Timeouts.Promise.allSettled()– wartet, bis alle Promises entweder erfüllt oder abgelehnt sind, und gibt den Status jedes einzelnen zurück.Promise.any()– liefert das erste erfolgreich erfüllte Promise und ignoriert abgelehnte, solange noch eine Option übrig ist.
Damit lassen sich robuste Strategien für unzuverlässige Netzwerke und Third-Party-APIs bauen. Für eine saubere Auswertung lohnt sich später oft ein Blick in Logging-Lösungen, wie im Beitrag zu sauberem Logging beschrieben – auch wenn dort eine andere Sprache genutzt wird, sind die Muster ähnlich.
Typische Fehler mit Promises vermeiden
Häufige Stolperfallen bei then-Ketten
Auch wenn Promises viele Probleme lösen, gibt es typische Fallstricke:
- Kein return in then – wenn im
.then()kein Wert oder Promise zurückgegeben wird, erhält der nächste Schrittundefined. Das führt schnell zu unerwarteten Fehlern. - Gemischter Stil – in einer Funktion wild
.then()undasync/awaitmischen, macht den Ablauf schwerer nachvollziehbar. Besser: pro Funktionsblock für einen Stil entscheiden. - Fehlendes catch – ohne Fehlerbehandlung gehen abgelehnte Promises gerne „unter“ und können das Debugging erschweren.
Wer generell Wert auf sauberen, gut lesbaren Code legt, profitiert von Prinzipien wie in Clean Code für JavaScript beschrieben – Promises passen dort gut hinein.
Unbehandelte Promise-Rejections erkennen
Viele Browser und Laufzeiten warnen inzwischen, wenn ein Promise abgelehnt wird, ohne dass irgendwo ein .catch() oder ein try/catch mit await existiert. Das ist ein Zeichen dafür, dass im Fehlerfall Logik fehlt – etwa eine Nutzerinfo oder ein Fallback.
Praxis-Tipp: Für zentrale Daten-Layer oder API-Clients lohnt sich eine gemeinsame Fehlerbehandlung, damit Fehler nicht an zig Stellen separat behandelt werden müssen.
Praxisleitfaden: Wo Promises im Alltag helfen
Typische Einsatzszenarien in Frontend und Backend
Einige klassische Fälle, in denen Promises fast immer eine Rolle spielen:
- HTTP-Requests zu REST- oder GraphQL-APIs.
- Zugriff auf Browser-APIs wie Geolocation, Clipboard oder IndexedDB.
- Dateioperationen im Node.js-Backend, wenn moderne, Promise-basierte Methoden verwendet werden.
- Animations- oder Timing-Logik, die über einfache
setTimeout-Aufrufe hinausgeht.
Gerade bei APIs ist das Zusammenspiel mit einem guten API-Design wichtig. Wer REST- und GraphQL-Konzepte besser verstehen möchte, findet Grundlagen im Artikel zu REST vs. GraphQL.
So baust du schrittweise eine Promise-Strategie auf
Statt alle bestehenden Projekte auf einmal umzubauen, ist ein schrittweises Vorgehen sinnvoll.
Mini-Ratgeber: Promises systematisch im Projekt etablieren
- Start mit einem kleinen, überschaubaren Modul (z. B. ein einzelner Fetch-Request), das von Callbacks auf Promises umgestellt wird.
- Entscheiden, ob im Projekt eher
.then()oderasync/awaitStandard sein soll – und das im Team dokumentieren. - Für alle zentralen API-Aufrufe eine gemeinsame Fehlerbehandlung definieren, etwa durch eine Utility-Funktion, die Promises auswertet und einheitliche Fehlerobjekte liefert.
- Lang laufende oder mehrere parallele Requests mit
Promise.all()kapseln, damit im UI klar ist, wann „alles fertig“ ist. - In Code-Reviews gezielt nach vergessenen
return-Anweisungen in.then()und fehlendencatch-Blöcken schauen.
Kurze So-geht’s-Box: Einstieg in JavaScript Promises
- Verstehen, dass ein Promise nur ein Objekt ist, das einen zukünftigen Wert oder Fehler beschreibt.
- Mit einem einfachen
fetch()-Beispiel üben:.then()für Erfolg,.catch()für Fehler,.finally()für Aufräumarbeiten. - Nach und nach
async/awaiteinsetzen, um Promise-Code lesbarer zu schreiben. - Für mehrere Requests
Promise.all()nutzen und bewusst über Fehlerszenarien nachdenken. - Im Team einen Stil für Promises und Fehlerbehandlung festlegen, um einheitlichen Code zu fördern.
FAQ zu JavaScript Promises
Ist async/await besser als then und catch?
Weder das eine noch das andere ist grundsätzlich besser. async/await macht lineare Abläufe leichter lesbar, besonders für Einsteiger. .then() eignet sich gut, wenn viele kleine Transformationsschritte hintereinander passieren oder wenn eine Library bereits stark auf Ketten setzt. In vielen Projekten werden beide Varianten kombiniert, aber pro Funktion sollte ein Stil bevorzugt werden.
Blockiert await meinen JavaScript-Code?
await blockiert nur die aktuelle async-Funktion, nicht den gesamten JavaScript-Thread. Die Event Loop arbeitet im Hintergrund weiter, andere Events können reagieren. Deshalb ist es aber wichtig, kein await in heiß laufenden Schleifen einzubauen, wenn sich Anfragen auch bündeln oder parallelisieren lassen.
Kann man Promises auch ohne async/await verwenden?
Ja. Promises wurden ursprünglich mit .then(), .catch() und .finally() eingeführt. async/await kam später als syntaktische Vereinfachung hinzu. Technisch läuft alles weiterhin über Promises, auch wenn im Code nur async und await zu sehen sind.
