Asynchrones JavaScript

Asynchrones JavaScript

JavaScript führt immer nur eine Sache gleichzeitig aus, friert jedoch nie ein, während es wartet. Der Event Loop, Callbacks, Promises und async/await sind die Mechanismen, die dies ermöglichen.

intermediate16 min readUpdated 15. Sept. 2026
js
// user.js
async function loadUser(id) {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error("User not found");
  return res.json();
}

const user = await loadUser(42);
console.log(user.name);
Ausführungsmodell
Single-threaded Event Loop
Erste Lösung
Callbacks
Moderne Lösung
Promises + async/await
Promise-Zustände
pending, fulfilled, rejected
Parallel ausführen
Promise.all / allSettled
Keyword
async / await

Praxis

Den Event Loop beobachten

Die Reihenfolge der Nachrichten zeigt, wie der Event Loop Aufgaben plant.

Den Event Loop beobachten

Die Reihenfolge der Nachrichten zeigt, wie der Event Loop Aufgaben plant.

Warum es wichtig ist

Warum Async wichtig ist

Blockiert niemals die Seite

Langsame Aufgaben werden außerhalb des Main-Threads erledigt, während die UI reaktionsfähig bleibt, sodass Benutzer nie vor einem eingefrorenen Tab sitzen.

Liest sich wie synchroner Code

async/await erlaubt es dir, asynchrone Logik von oben nach unten zu schreiben, ohne Callbacks zu verschachteln oder .then-Blöcke zu chainen.

Für das Netzwerk gebaut

Jeder fetch, Timer, Datei-Read und Datenbankaufruf in modernem JavaScript ist asynchron – das ist also essenziell und nicht optional.

Das Gesamtbild

Die drei Puzzleteile

Asynchrones JavaScript ist kein einzelnes Feature, sondern drei übereinander gestapelte Ebenen: der Event Loop, Promises und eine Syntax, die wie synchroner Code gelesen wird.

Der Event Loop

Scheduling

Ein Call Stack, eine Task Queue und eine Microtask Queue entscheiden über die Reihenfolge, in der dein Code tatsächlich ausgeführt wird.

Promises

Repräsentation

Ein Objekt, das einen zukünftigen Wert repräsentiert, mit einem sauberen Weg, Aufgaben zu verketten und Fehler zu behandeln.

async / await

Syntax

Eine dünne Schicht über Promises, die asynchronen Code so aussehen und lesen lässt wie gewöhnlichen synchronen Code.

Async auf einen Blick

Die Werkzeuge in deinem Async-Toolkit

Der Event Loop

Führt gewartete Aufgaben aus, sobald der aktuelle synchrone Code abgeschlossen ist.

Callbacks

Das ursprüngliche Muster – übergib eine Funktion, die ausgeführt wird, wenn die Arbeit erledigt ist.

Promises

Objekte mit drei Zuständen, die es ermöglichen, Erfolgs- und Fehler-Handler zu verketten.

Combinators

all, allSettled, race und any koordinieren mehrere Promises gleichzeitig.

async / await

Pausiert eine Funktion bei await, ohne den Rest des Programms zu blockieren.

Fehlerbehandlung

.catch und try/catch verwandeln rejected Promises in normale, behandelbare Fehler.

Eine kurze Geschichte

Von Callbacks zu async/await

  1. 2005

    AJAX popularisiert Callbacks

    Gmail und Google Maps zeigen der Welt, dass Seiten aktualisiert werden können, ohne neu zu laden.

    05
  2. 2009

    Node.js setzt auf Async

    Serverseitiges JavaScript macht non-blocking I/O zum Standardweg für den Backend-Bau.

    09
  3. 2015

    Promises in ES6

    Das Promise-Objekt wird standardisiert und gibt asynchronem Code einen gemeinsamen Vertrag.

    15
  4. 2017

    async / await

    ES2017 fügt die Syntax hinzu, die asynchronen Code wie synchronen Code lesbar macht.

    17
  5. Heute

    Async überall

    Fetch, Streams, Worker und die meisten modernen APIs sind standardmäßig Promise-basiert.

    Heute

Der vollständige Leitfaden

Asynchrones JavaScript: Alles was Sie wissen müssen

Warum es asynchrones JavaScript gibt

Computer erledigen viele Dinge langsam: das Lesen von Dateien, Datenbankabfragen oder das Warten auf die Antwort eines Servers. Wenn JavaScript bei jedem dieser Vorgänge anhalten und warten würde, würde die Seite einfrieren und die Nutzer würden die Seite verlassen. Asynchrone Programmierung ermöglicht es der Sprache, weiterzuarbeiten, während langsame Aufgaben an anderer Stelle ausgeführt werden.

Das passende mentale Modell hierfür ist ein Restaurant. Ein einziger Kellner nimmt Ihre Bestellung auf, gibt sie an die Küche weiter und bedient sofort den nächsten Tisch. Wenn Ihr Essen fertig ist, läutet die Küche eine Glocke und der Kellner bringt es an den Tisch. Der Kellner steht niemals in der Küche und schaut dem Essen beim Kochen zu. JavaScript ist dieser Kellner und der event loop ist die Glocke.

Der Call Stack und der Event Loop

JavaScript besitzt einen einzigen Call Stack. Jeder Funktionsaufruf pusht einen Frame auf den Stack; sobald die Funktion zurückkehrt, wird der Frame wieder entfernt (pop). Da es nur einen Stack gibt, kann immer nur eine Operation gleichzeitig ausgeführt werden.

Asynchrone APIs werden von der Host-Umgebung bereitgestellt, nicht von der Sprache selbst. Wenn Sie setTimeout, fetch oder einen Datei-Read aufrufen, übernimmt der Browser oder Node.js das Warten. Sobald die Arbeit abgeschlossen ist, wird der entsprechende Callback in eine Queue eingereiht. Der Event Loop prüft kontinuierlich: Ist der Call Stack leer? Wenn ja, wird der nächste Callback aus der Queue genommen und ausgeführt.

// order.js
console.log("start");

setTimeout(() => console.log("timeout"), 0);

Promise.resolve().then(() => console.log("promise"));

console.log("end");

// start, end, promise, timeout

Zwei Details sind hierbei entscheidend. Erstens wird synchroner Code immer vollständig ausgeführt, bevor irgendein Callback startet. Zweitens gibt es zwei Queues: Microtasks (Promise-Callbacks) werden unmittelbar nach dem aktuellen Task ausgeführt, noch vor den eigentlichen Tasks (Timer, I/O). Aus diesem Grund loggt promise vor timeout.

Callbacks

Die ursprüngliche Methode zur Verarbeitung asynchroner Ergebnisse ist der Callback – eine Funktion, die man übergibt, damit sie zu einem späteren Zeitpunkt aufgerufen wird.

// callback.js
function getUser(id, callback) {
  setTimeout(() => {
    callback({ id, name: "Ada" });
  }, 300);
}

getUser(1, (user) => {
  console.log(user.name);
});

Callbacks funktionieren zwar, lassen sich aber schlecht kombinieren. Wenn jeder Schritt vom vorherigen abhängt, entstehen Verschachtelungen, und das Ergebnis ist die sogenannte callback hell: Code, der diagonal über den Bildschirm wandert und die Fehlerbehandlung extrem mühsam macht.

// hell.js
getUser(1, (user) => {
  getPosts(user.id, (posts) => {
    getComments(posts[0].id, (comments) => {
      // three levels deep and counting
    });
  });
});

Promises

Ein Promise ist ein Objekt, das einen Wert repräsentiert, der möglicherweise noch nicht existiert. Es befindet sich in einem von drei Zuständen:

  • pending — die Operation läuft noch.
  • fulfilled — sie wurde erfolgreich mit einem Wert abgeschlossen.
  • rejected — sie ist mit einer Fehlermeldung fehlgeschlagen.

Sobald ein Promise abgeschlossen ist, ändert es seinen Zustand nicht mehr. Über then, catch und finally hängen Sie Handler an.

// promise.js
function getUser(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id > 0) resolve({ id, name: "Ada" });
      else reject(new Error("Invalid id"));
    }, 300);
  });
}

getUser(1)
  .then((user) => console.log(user.name))
  .catch((error) => console.error(error.message))
  .finally(() => console.log("done"));

Der entscheidende Vorteil ist das Chaining. Jedes then gibt ein neues Promise zurück. Anstatt die Aufrufe zu verschachteln, können Sie die Sequenz flach halten und jeden Fehler in einem einzigen catch am Ende abfangen.

// chain.js
getUser(1)
  .then((user) => getPosts(user.id))
  .then((posts) => getComments(posts[0].id))
  .then((comments) => console.log(comments))
  .catch((error) => console.error("Something failed:", error));

Promises kombinieren

Wenn mehrere asynchrone Operationen involviert sind, bietet der Promise-Konstruktor Kombinatoren an.

// combine.js
const [user, posts, settings] = await Promise.all([
  getUser(1),
  getPosts(1),
  getSettings(),
]);

const results = await Promise.allSettled([getUser(1), getUser(-1)]);

const fastest = await Promise.race([getFromCache(), getFromNetwork()]);

const firstSuccess = await Promise.any([mirrorA(), mirrorB()]);

Promise.all führt alles parallel aus und lehnt ab (reject), wenn eine einzige Promise abgelehnt wird. Promise.allSettled wartet auf alle und berichtet über jedes Ergebnis. Promise.race löst sich mit der ersten Promise auf, die einen Status erreicht (settle), und Promise.any löst sich mit der ersten auf, die erfolgreich ist (resolve). Die Wahl des richtigen Kombinators verändert dein Fehlerverhalten dramatisch.

async und await

async und await sind Syntax-Erweiterungen, die auf Promises aufbauen. Wenn eine Funktion als async markiert wird, gibt sie immer ein Promise zurück. Innerhalb dieser Funktion pausiert await die Ausführung, bis ein Promise abgeschlossen ist, und setzt sie dann mit dem aufgelösten Wert fort – ohne den Rest des Programms zu blockieren.

// await.js
async function loadDashboard() {
  try {
    const user = await getUser(1);
    const posts = await getPosts(user.id);
    return { user, posts };
  } catch (error) {
    console.error("Failed to load dashboard", error);
    throw error;
  }
}

Das liest sich wie synchroner Code, was genau das Ziel ist. Fehler werden mit gewöhnlichen try/catch-Blöcken abgefangen, und finally läuft weiterhin. Da die Funktion ein Promise zurückgibt, können Aufrufer dieses await oder wie gewohnt .then verketten.

Sequenziell vs. parallel

Ein subtiler, aber kostspieliger Fehler besteht darin, unabhängige Aufgaben nacheinander mit await abzuarbeiten. Jedes await wartet auf das vorherige, sodass die Gesamtzeit die Summe aller Einzelzeiten ist. Wenn die Aufgaben nicht voneinander abhängen, sollten sie gleichzeitig gestartet werden.

// parallel.js
// Sequential — slow, ~600ms
const a = await slowTask("a");
const b = await slowTask("b");

// Parallel — fast, ~300ms
const [x, y] = await Promise.all([slowTask("x"), slowTask("y")]);

Nutzen Sie sequenzielle await-Aufrufe nur dann, wenn ein späterer Schritt tatsächlich ein Ergebnis eines vorherigen Schritts benötigt – zum Beispiel, wenn erst ein Benutzer abgerufen werden muss, bevor dessen Posts geladen werden können.

Fehlerbehandlung in async-Code

Abgelehnte Promises, die nicht abgefangen werden, werden zu unhandled rejections. Diese können Node.js-Prozesse zum Absturz bringen und die Konsole in Browsern überfluten.

// errors.js
try {
  const res = await fetch("/api/data");
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const data = await res.json();
} catch (error) {
  showMessage("Could not load data. Please try again.");
} finally {
  hideSpinner();
}

Bevorzuge try/catch um awaited-Aufrufe, wirf Error-Objekte (keine Strings) und fange Fehler immer ab oder wirf sie bewusst erneut. Der Error Handling Guide geht detaillierter auf Strategien und benutzerorientierte Fehlermeldungen ein.

Häufige Fallstricke

  • await vergessen. Die Funktion gibt ein Promise zurück, nicht den Wert, sodass die nächste Zeile zu früh ausgeführt wird.
  • forEach bei async Callbacks. forEach ignoriert zurückgegebene Promises; verwenden Sie stattdessen for...of oder Promise.all zusammen mit map.
  • Awaiting in einer Schleife, obwohl Parallelisierung möglich wäre. Es funktioniert zwar, ist aber unnötig langsam.
  • Fehlendes Return innerhalb von then. Ein fehlendes return unterbricht die Kette und führt zum Verlust des Wertes.
  • Annahme einer festen Reihenfolge. Nur await und die Microtask-Reihenfolge garantieren die Sequenz; konkurrierende Tasks schließen ab, wann immer sie fertig sind.
  • Fehler verschlucken durch einen leeren catch-Block, wodurch der Bug verborgen bleibt, den Sie eigentlich hätten sehen müssen.

Best Practices

  • Bevorzuge async/await gegenüber langen .then-Ketten für eine bessere Lesbarkeit.
  • Führe unabhängige Aufgaben parallel mit Promise.all oder Promise.allSettled aus.
  • Behandle Ablehnungen (Rejections) immer, und wenn es nur dazu dient, sie zu loggen und erneut zu werfen.
  • Halte async-Funktionen klein und weise ihnen jeweils nur eine einzige Verantwortung zu.
  • Nutze Promise.allSettled, wenn Teilergebnisse akzeptabel sind.
  • Brich Aufgaben, die nicht mehr benötigt werden, mit einem AbortController ab.
  • Zeige Lade- und Fehlerzustände an, damit die Nutzer verstehen, was gerade passiert.

Wie geht es weiter?

Du verfügst nun über das komplette Toolkit für asynchrone Programmierung. Setze es mit der Fetch API in die Praxis um, wo Promises und await jede Netzwerkanfrage steuern, und mache deinen Code mit den Mustern aus dem Bereich Error Handling robuster.

Chaining vs. Awaiting

async/await entfernt verschachtelte Callbacks und macht die Sequenz offensichtlich.

Bevorzugt
async function load() {
  const user = await getUser(1);
  const posts = await getPosts(user.id);
  return posts;
}
Vermeiden
function load() {
  return getUser(1).then((user) => {
    return getPosts(user.id).then((posts) => {
      return posts;
    });
  });
}

Unabhängige Aufgaben

Wenn die Anfragen nicht voneinander abhängen, starte sie gemeinsam mit Promise.all.

Bevorzugt
const [user, posts] = await Promise.all([
  getUser(1),
  getPosts(1),
]);
Vermeiden
const user = await getUser(1);
const posts = await getPosts(1);
// runs one after the other

Häufig gestellte Fragen

Häufig gestellte Fragen

Keep learning

Related topics from the roadmap.

$ Lernen Sie jetzt

Bereit, Async JavaScript zu lernen?

Unser interaktives Tutorial führt Sie Schritt für Schritt durch Async JavaScript — mit Quizzen und echtem Code, den Sie im Browser ausführen können.