JavaScript Fortgeschritten

Synchron vs. Asynchron in JavaScript

Synchron vs. Asynchron in JavaScript

Bevor du Promises und async/await verstehen kannst, musst du den fundamentalen Unterschied zwischen synchronem und asynchronem Code verstehen.

Synchroner Code

Synchroner Code wird Zeile für Zeile ausgeführt. Jede Zeile wartet, bis die vorherige fertig ist.

console.log("Erste Zeile");
console.log("Zweite Zeile");
console.log("Dritte Zeile");

// Ausgabe (garantierte Reihenfolge):
// Erste Zeile
// Zweite Zeile
// Dritte Zeile

Das Problem mit synchronem Code

// Stell dir vor, diese Funktion braucht 5 Sekunden
function ladeDatenVomServer() {
    // ... warte 5 Sekunden ...
    return daten;
}

console.log("Start");
const daten = ladeDatenVomServer();  // Browser friert 5 Sekunden ein!
console.log("Daten geladen");
console.log("Weiter...");

// Während dieser 5 Sekunden:
// - Keine Klicks möglich
// - Keine Animationen
// - Seite reagiert nicht
// = Schlechte User Experience!

Asynchroner Code

Asynchroner Code blockiert nicht. Langsame Operationen laufen im Hintergrund, und der Rest des Codes läuft weiter.

console.log("Start");

setTimeout(() => {
    console.log("Nach 2 Sekunden");
}, 2000);

console.log("Ende");

// Ausgabe:
// Start
// Ende
// (2 Sekunden Pause)
// Nach 2 Sekunden

Die Reihenfolge ist nicht von oben nach unten!

Warum brauchen wir asynchronen Code?

JavaScript läuft in einem Single Thread - es kann nur eine Sache gleichzeitig tun. Aber moderne Webapps müssen:

  • Daten vom Server laden
  • Dateien lesen/schreiben
  • Auf User-Input warten
  • Timer verwalten
  • Animationen abspielen

All das gleichzeitig - ohne die UI zu blockieren.

Die Event Loop

JavaScript löst das mit der Event Loop:

┌─────────────────────────────┐
│        Call Stack           │  ← Führt synchronen Code aus
└─────────────────────────────┘

            │ Nimmt Tasks

┌─────────────────────────────┐
│       Task Queue            │  ← Fertige async Callbacks warten hier
└─────────────────────────────┘

            │ Schiebt fertige Tasks

┌─────────────────────────────┐
│     Web APIs / Browser      │  ← setTimeout, fetch, Events laufen hier
└─────────────────────────────┘
console.log("1");

setTimeout(() => {
    console.log("2");
}, 0);  // Auch mit 0ms!

console.log("3");

// Ausgabe:
// 1
// 3
// 2  ← Kommt zuletzt, obwohl Timer 0ms!

// Warum?
// 1. "1" geht auf Call Stack → wird ausgeführt
// 2. setTimeout geht zu Web API → Timer startet
// 3. "3" geht auf Call Stack → wird ausgeführt
// 4. Timer fertig → Callback in Queue
// 5. Call Stack leer → Callback aus Queue auf Stack
// 6. "2" wird ausgeführt

Asynchrone Patterns in JavaScript

1. Callbacks (älteste Methode)

function ladeDaten(callback) {
    setTimeout(() => {
        const daten = { id: 1, name: "Max" };
        callback(daten);
    }, 1000);
}

console.log("Lade...");

ladeDaten((daten) => {
    console.log("Daten:", daten);
});

console.log("Weiter...");

// Ausgabe:
// Lade...
// Weiter...
// (1 Sekunde)
// Daten: { id: 1, name: "Max" }

2. Promises (ES6)

function ladeDaten() {
    return new Promise((resolve) => {
        setTimeout(() => {
            const daten = { id: 1, name: "Max" };
            resolve(daten);
        }, 1000);
    });
}

console.log("Lade...");

ladeDaten().then((daten) => {
    console.log("Daten:", daten);
});

console.log("Weiter...");

// Gleiche Ausgabe wie Callbacks

3. Async/Await (ES2017)

async function main() {
    console.log("Lade...");

    const daten = await ladeDaten();
    console.log("Daten:", daten);

    console.log("Weiter...");
}

main();

// Ausgabe (anders!):
// Lade...
// (1 Sekunde)
// Daten: { id: 1, name: "Max" }
// Weiter...

Häufige asynchrone Operationen

Timer

// Nach Zeit ausführen
setTimeout(() => {
    console.log("Nach 1 Sekunde");
}, 1000);

// Wiederholt ausführen
const intervalId = setInterval(() => {
    console.log("Alle 2 Sekunden");
}, 2000);

// Stoppen
clearInterval(intervalId);

Netzwerk-Anfragen (fetch)

// fetch ist asynchron!
fetch("https://api.example.com/users")
    .then(response => response.json())
    .then(users => console.log(users));

console.log("Fetch gestartet");

// Ausgabe:
// Fetch gestartet
// (irgendwann später)
// [User-Daten]

Event Listener

// Events sind asynchron - wir wissen nicht WANN geklickt wird
button.addEventListener("click", () => {
    console.log("Irgendwann geklickt!");
});

console.log("Listener registriert");

// Ausgabe:
// Listener registriert
// (Klick passiert)
// Irgendwann geklickt!

Das Problem verstehen

Rückgabewert von asynchronem Code

// Das funktioniert NICHT wie erwartet!
function holeDaten() {
    let ergebnis;

    fetch("https://api.example.com/data")
        .then(response => response.json())
        .then(data => {
            ergebnis = data;
        });

    return ergebnis;  // undefined! Fetch ist noch nicht fertig!
}

const daten = holeDaten();
console.log(daten);  // undefined

Die Lösung: Promise zurückgeben

// Richtig: Promise zurückgeben
function holeDaten() {
    return fetch("https://api.example.com/data")
        .then(response => response.json());
}

holeDaten().then(daten => {
    console.log(daten);  // Jetzt haben wir die Daten!
});

Synchron vs. Asynchron - Vergleich

AspektSynchronAsynchron
AusführungBlockierendNicht blockierend
ReihenfolgeGarantiertNicht garantiert
UIKann einfrierenBleibt responsive
Fehlerhandlingtry/catch.catch() / try/catch mit await
RückgabewertDirektPromise

Wann was verwenden?

Synchron

  • Einfache Berechnungen
  • String-Operationen
  • Array-Manipulationen
  • Alles was sofort fertig ist
// Synchron OK
const summe = zahlen.reduce((a, b) => a + b);
const gross = text.toUpperCase();
const filtered = array.filter(x => x > 10);

Asynchron

  • Netzwerk-Anfragen (fetch, AJAX)
  • Datei-Operationen
  • Timer
  • Datenbank-Abfragen
  • Alles was “warten” erfordert
// Muss asynchron sein
const response = await fetch(url);
await writeFile(path, content);
await delay(1000);
const user = await database.getUser(id);

Zusammenfassung

// Synchron - blockiert, garantierte Reihenfolge
console.log("A");
console.log("B");
// Ausgabe: A, B

// Asynchron - blockiert nicht, Reihenfolge unsicher
console.log("A");
setTimeout(() => console.log("B"), 0);
console.log("C");
// Ausgabe: A, C, B

// Merksatz:
// - Synchron = sofort, der Reihe nach
// - Asynchron = später, wenn fertig

Der Weg zu modernem Code

Callbacks → Promises → async/await
   |           |           |
   V           V           V
 "Callback    "Besser     "Sieht aus
   Hell"     lesbar"      wie synchron"

In den nächsten Tutorials lernst du Promises und async/await im Detail!


Übung: Was ist die Ausgabe dieses Codes? Überlege erst, dann teste!

console.log("Start");

setTimeout(() => console.log("Timeout 1"), 0);
setTimeout(() => console.log("Timeout 2"), 0);

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

console.log("Ende");