Fortgeschritten202025-01-15

JavaScript Async/Await & Promises - Asynchrone Programmierung

Lerne asynchrone Programmierung in JavaScript. Von Callbacks über Promises bis zu Async/Await - alles verständlich erklärt mit praktischen Beispielen.

#javascript#async#await#promises#asynchron#callbacks

JavaScript Async/Await & Promises

JavaScript ist single-threaded, kann aber trotzdem mehrere Dinge "gleichzeitig" machen. Das Geheimnis? Asynchrone Programmierung!

Das Problem: Synchroner Code blockiert

Synchroner Code - Alles wartet
console.log('Start');

// Simuliere lange Berechnung (3 Sekunden)
function longCalculation() {
  const start = Date.now();
  while (Date.now() - start < 3000) {
    // Blockiert alles!
  }
  return 'Fertig';
}

console.log(longCalculation()); // 3 Sekunden Wartezeit
console.log('Ende');

// Output:
// Start
// ... 3 Sekunden Warten ...
// Fertig
// Ende

Problem: Während longCalculation() läuft, ist die gesamte Seite eingefroren! ❌

Die Lösung: Asynchroner Code

Callbacks (Old School)

Callbacks - Der alte Weg
console.log('Start');

// Asynchrone Funktion mit Callback
function fetchData(callback) {
  setTimeout(() => {
    const data = { name: 'Max', age: 25 };
    callback(data);
  }, 2000);
}

fetchData((result) => {
  console.log('Daten:', result);
});

console.log('Ende');

// Output:
// Start
// Ende
// ... 2 Sekunden später ...
// Daten: { name: 'Max', age: 25 }

Vorteil: Code blockiert nicht mehr! ✅ Nachteil: "Callback Hell" bei verschachtelten Aufrufen ❌

Callback Hell - Das Problem

Callback Hell - Schwer lesbar!
// ❌ Callback Hell - Pyramide des Grauens
getUser(1, (user) => {
  getOrders(user.id, (orders) => {
    getOrderDetails(orders[0].id, (details) => {
      getShippingInfo(details.shippingId, (shipping) => {
        console.log(shipping);
        // ... noch mehr Verschachtelung ...
      });
    });
  });
});

Promises - Die moderne Lösung

Ein Promise ist ein Versprechen für einen zukünftigen Wert.

Promise States (Zustände)

pending   → noch nicht fertig (Anfangszustand)
fulfilled → erfolgreich abgeschlossen (resolved)
rejected  → fehlgeschlagen

Promise erstellen

Promise erstellen
// Promise erstellen
const myPromise = new Promise((resolve, reject) => {
  // Asynchrone Operation
  setTimeout(() => {
    const success = true;

    if (success) {
      resolve('Erfolgreich!'); // Promise erfüllt
    } else {
      reject('Fehler aufgetreten!'); // Promise abgelehnt
    }
  }, 2000);
});

// Promise verwenden
myPromise
  .then((result) => {
    console.log(result); // "Erfolgreich!"
  })
  .catch((error) => {
    console.error(error);
  });

Promise Chaining

Promises verketten - Viel besser als Callbacks!
// ✅ Promise Chain - Lesbar!
getUser(1)
  .then((user) => {
    console.log('User:', user);
    return getOrders(user.id);
  })
  .then((orders) => {
    console.log('Orders:', orders);
    return getOrderDetails(orders[0].id);
  })
  .then((details) => {
    console.log('Details:', details);
    return getShippingInfo(details.shippingId);
  })
  .then((shipping) => {
    console.log('Shipping:', shipping);
  })
  .catch((error) => {
    console.error('Fehler:', error);
  });

Praktisches Beispiel: Daten laden

Daten von API laden (simuliert)
function fetchUserData(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (userId > 0) {
        resolve({
          id: userId,
          name: 'Anna Schmidt',
          email: 'anna@example.com'
        });
      } else {
        reject('Ungültige User-ID');
      }
    }, 1000);
  });
}

// Verwenden
fetchUserData(123)
  .then((user) => {
    console.log('Empfangen:', user);
  })
  .catch((error) => {
    console.error('Fehler:', error);
  });

Async/Await - Noch besser!

Async/Await macht asynchronen Code wie synchronen Code aussehen!

Grundlagen

async/await Basics
// Funktion mit 'async' markieren
async function loadData() {
  // 'await' pausiert bis Promise fertig ist
  const user = await fetchUserData(123);
  console.log(user);
  return user;
}

// Async-Funktionen geben IMMER ein Promise zurück
loadData()
  .then(() => console.log('Fertig!'));

Fehlerbehandlung mit try/catch

Fehlerbehandlung mit async/await
async function loadUserData() {
  try {
    const user = await fetchUserData(123);
    console.log('User geladen:', user);

    const orders = await fetchOrders(user.id);
    console.log('Orders geladen:', orders);

    return { user, orders };
  } catch (error) {
    console.error('Fehler beim Laden:', error);
    throw error; // Weitergeben
  }
}

// Verwenden
loadUserData()
  .then((data) => {
    console.log('Alle Daten:', data);
  })
  .catch((error) => {
    console.error('Finaler Fehler:', error);
  });

Vorher vs. Nachher

Promise Chain vs. Async/Await
// ❌ Mit Promises (funktioniert, aber unübersichtlich)
function getUserInfo(userId) {
  return fetchUser(userId)
    .then((user) => {
      return fetchOrders(user.id)
        .then((orders) => {
          return { user, orders };
        });
    })
    .catch((error) => {
      console.error(error);
    });
}

// ✅ Mit Async/Await (klar und lesbar!)
async function getUserInfo(userId) {
  try {
    const user = await fetchUser(userId);
    const orders = await fetchOrders(user.id);
    return { user, orders };
  } catch (error) {
    console.error(error);
  }
}

Multiple Promises gleichzeitig

Promise.all() - Warte auf alle

Promise.all() - Parallel ausführen
async function loadAllData() {
  try {
    // Alle Promises gleichzeitig starten!
    const [user, posts, comments] = await Promise.all([
      fetchUser(1),
      fetchPosts(),
      fetchComments()
    ]);

    console.log('Alles geladen:', { user, posts, comments });
    return { user, posts, comments };
  } catch (error) {
    // Wenn EINES fehlschlägt, wird alles abgebrochen
    console.error('Fehler:', error);
  }
}

loadAllData();

Wichtig: Promise.all() schlägt fehl, wenn ein einziges Promise rejected wird!

Promise.allSettled() - Warte auf alle, egal ob Erfolg oder Fehler

Promise.allSettled() - Fehlertoleranter
async function loadDataSafely() {
  const results = await Promise.allSettled([
    fetchUser(1),
    fetchUser(999), // Fehlschlägt
    fetchPosts()
  ]);

  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      console.log(`Promise ${index}: Erfolg`, result.value);
    } else {
      console.log(`Promise ${index}: Fehler`, result.reason);
    }
  });
}

loadDataSafely();

// Output:
// Promise 0: Erfolg { id: 1, name: 'Max' }
// Promise 1: Fehler User not found
// Promise 2: Erfolg [{ id: 1, title: '...' }, ...]

Promise.race() - Erster gewinnt

Promise.race() - Schnellster zählt
async function fastestServer() {
  const result = await Promise.race([
    fetchFromServer1(),
    fetchFromServer2(),
    fetchFromServer3()
  ]);

  console.log('Schnellster Server antwortet:', result);
  return result;
}

// Use Case: Timeout implementieren
async function fetchWithTimeout(url, timeout = 5000) {
  return Promise.race([
    fetch(url),
    new Promise((_, reject) =>
      setTimeout(() => reject('Timeout!'), timeout)
    )
  ]);
}

Reale Beispiele

Beispiel 1: Daten von API laden

Echte API-Anfrage
async function loadPosts() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts');

    if (!response.ok) {
      throw new Error(`HTTP Error: ${response.status}`);
    }

    const posts = await response.json();
    console.log('Posts geladen:', posts);
    return posts;
  } catch (error) {
    console.error('Fehler beim Laden:', error);
    throw error;
  }
}

loadPosts();

Beispiel 2: Sequentielles Laden mit Progress

Schritt-für-Schritt laden
async function loadDashboard() {
  console.log('Lade Dashboard...');

  // Schritt 1
  console.log('1/3: Lade User...');
  const user = await fetchUser(1);
  console.log('✓ User geladen');

  // Schritt 2
  console.log('2/3: Lade Posts...');
  const posts = await fetchPosts(user.id);
  console.log('✓ Posts geladen');

  // Schritt 3
  console.log('3/3: Lade Statistiken...');
  const stats = await fetchStats(user.id);
  console.log('✓ Statistiken geladen');

  console.log('Dashboard komplett!');
  return { user, posts, stats };
}

loadDashboard();

Beispiel 3: Retry-Logic

Automatisches Wiederholen bei Fehler
async function fetchWithRetry(url, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      console.log(`Versuch ${i + 1}/${maxRetries}...`);
      const response = await fetch(url);

      if (response.ok) {
        return await response.json();
      }
    } catch (error) {
      console.error(`Versuch ${i + 1} fehlgeschlagen:`, error);

      if (i === maxRetries - 1) {
        throw new Error('Alle Versuche fehlgeschlagen');
      }

      // Warte 1 Sekunde vor erneutem Versuch
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
  }
}

fetchWithRetry('https://api.example.com/data');

📝 Quiz

Was gibt eine async-Funktion IMMER zurück?

Tipps & Tricks

Top-Level Await (Moderne Browser)

In modernen Browsern kannst du await auch außerhalb von async-Funktionen nutzen:

// In Modulen (type="module")
const data = await fetchData();
console.log(data);

Async/Await mit Array Methods

// ❌ FALSCH - forEach funktioniert nicht mit async!
array.forEach(async (item) => {
  await processItem(item);
});

// ✅ RICHTIG - for...of verwenden
for (const item of array) {
  await processItem(item);
}

// ✅ RICHTIG - Parallel mit Promise.all
await Promise.all(
  array.map(item => processItem(item))
);

Debugging Async Code

async function debug() {
  console.log('Start');

  const result = await fetchData();
  debugger; // Breakpoint hier setzen

  console.log('Result:', result);
}

Promise.any() - Erste erfolgreiche

// Nimm erste erfolgreiche Response
const fastSuccess = await Promise.any([
  fetchFromServer1(), // Langsam aber erfolgreich
  fetchFromServer2(), // Schnell aber Fehler
  fetchFromServer3()  // Mittel und erfolgreich
]);

Häufige Fehler

Fehler 1: await ohne async

FALSCH:

function getData() {
  const data = await fetchData(); // ❌ SyntaxError!
  return data;
}

RICHTIG:

async function getData() {
  const data = await fetchData(); // ✅
  return data;
}

Fehler 2: await in Schleife (langsam!)

FALSCH (langsam - nacheinander):

async function processUsers(users) {
  const results = [];

  for (const user of users) {
    const data = await fetchUserData(user.id); // Wartet jedes Mal!
    results.push(data);
  }

  return results;
}

RICHTIG (schnell - parallel):

async function processUsers(users) {
  const promises = users.map(user => fetchUserData(user.id));
  const results = await Promise.all(promises);
  return results;
}

Fehler 3: Fehlerbehandlung vergessen

FALSCH:

async function loadData() {
  const data = await fetchData(); // Was wenn Fehler?
  return data;
}

RICHTIG:

async function loadData() {
  try {
    const data = await fetchData();
    return data;
  } catch (error) {
    console.error('Fehler:', error);
    throw error;
  }
}

Fehler 4: Promise nicht awaiten

FALSCH:

async function saveData(data) {
  saveToDatabase(data); // Promise wird nicht abgewartet!
  console.log('Gespeichert'); // Lüge! Vielleicht noch nicht fertig
}

RICHTIG:

async function saveData(data) {
  await saveToDatabase(data); // Jetzt wirklich warten
  console.log('Gespeichert'); // Stimmt!
}
🎯

Zusammenfassung

Du hast gelernt:

  • ✅ JavaScript ist single-threaded, aber asynchron
  • ✅ Callbacks → Promises → Async/Await (Evolution)
  • ✅ Promises haben 3 Zustände: pending, fulfilled, rejected
  • async markiert Funktion als asynchron
  • await pausiert bis Promise fertig ist
  • try/catch für Fehlerbehandlung
  • Promise.all() für parallele Ausführung
  • Promise.race() für schnellste Response

Key Takeaways:

  • Async-Funktionen returnen IMMER ein Promise
  • await nur in async-Funktionen
  • Immer Fehlerbehandlung mit try/catch
  • Promise.all() für parallele Requests (schneller!)
  • await in Schleifen vermeiden (langsam!)

Best Practices:

  • Nutze async/await statt Promises (lesbarer)
  • Mehrere Requests? Promise.all() nutzen
  • Immer try/catch verwenden
  • Timeout mit Promise.race() implementieren

Praktische Übungen

Übung 1: Einfaches Promise

Erstelle ein Promise, das nach 2 Sekunden "Hallo Welt" zurückgibt.

Übung 2: API-Anfrage

Lade Daten von https://jsonplaceholder.typicode.com/users mit async/await.

Übung 3: Parallele Requests

Lade gleichzeitig:

  • Users
  • Posts
  • Comments

Mit Promise.all().

Übung 4: Fehlerbehandlung

Schreibe eine Funktion die:

  • Daten lädt
  • Bei Fehler 3x wiederholt
  • Nach 3 Fehlversuchen aufgibt

Übung 5: Loading Spinner

Erstelle:

async function loadDataWithSpinner() {
  showSpinner();
  try {
    const data = await fetchData();
    displayData(data);
  } catch (error) {
    showError(error);
  } finally {
    hideSpinner(); // Immer ausführen!
  }
}
JavaScriptLektion 12 von 17
71% abgeschlossen
Lektion abgeschlossen!

Gut gemacht! 🎉

Du hast "JavaScript Async/Await & Promises - Asynchrone Programmierung" abgeschlossen

Artikel bewerten

0.0 (0 Bewertungen)

Bitte einloggen um zu bewerten