Skip to content

Kapitel 6: Asynchrone Programmierung

🎯 Lernziele

In diesem Kapitel lernen Sie:

  • ✅ Synchron vs. Asynchron verstehen
  • ✅ Blockierend vs. Nicht-blockierend
  • ✅ Callback-Funktionen
  • ✅ Promises (Versprechen)
  • ✅ async/await (Moderne Methode)
  • ✅ "Callback Hell" vermeiden
  • ✅ Häufige Fehler bei asynchronem Code

6.1 Synchron vs. Asynchron

📖 Einfache Definition

BegriffBedeutungBeispiel
SynchronCode wird zeilenweise ausgeführt (nacheinander)Warten auf Dateilesen
AsynchronCode wird nicht blockiert ausgeführtWeiterarbeiten während Datei liest

🎨 Visuelle Erklärung

Synchrone Ausführung (Blockierend):
┌─────────────────────────────────┐
│  Task 1: Datei lesen...         │ ← Wartet!
│  Task 2: Berechnung           │ ← Muss warten
│  Task 3: Antwort senden       │ ← Muss warten
└─────────────────────────────────┘
Gesamtzeit = 10s + 2s + 1s = 13s

Asynchrone Ausführung (Nicht-blockierend):
┌─────────────────────────────────┐
│  Task 1: Datei lesen (Start)  │ ← Startet, läuft im Hintergrund
│  Task 2: Berechnung           │ ← Sofort ausgeführt!
│  Task 3: Antwort senden       │ ← Nach Task 2
│  ... (Datei fertig) ...       │ ← Wenn fertig, Callback ausführen
└─────────────────────────────────┘
Gesamtzeit ≈ 2s (Parallel!)

💡 Code-Beispiel

javascript
// ❌ Synchrone Methode (Blockierend - Schlecht!)
const fs = require('fs');
console.log('1. Starte Dateilesen...');
const daten = fs.readFileSync('datei.txt', 'utf8');  // Blockiert!
console.log('2. Datei gelesen:', daten);
console.log('3. Weiter mit anderem Code...');  // Muss warten!

// ✅ Asynchrone Methode (Nicht-blockierend - Gut!)
console.log('1. Starte Dateilesen...');
fs.readFile('datei.txt', 'utf8', (err, daten) => {
  console.log('2. Datei gelesen:', daten);
});
console.log('3. Weiter mit anderem Code...');  // Wird sofort ausgeführt!

Ausgabe (Asynchron):

1. Starte Dateilesen...
3. Weiter mit anderem Code...
2. Datei gelesen: [Inhalt]

6.2 Drei Methoden

📚 Methode 1: Callback-Funktionen (Traditionell)

Callback = Eine Funktion, die als Argument an eine andere Funktion übergeben wird.

javascript
// Grundlegende Callback-Struktur
function dateiLesen(pfad, callback) {
  fs.readFile(pfad, 'utf8', (err, daten) => {
    if (err) {
      callback(err, null);  // Fehler weitergeben
      return;
    }
    callback(null, daten);   // Ergebnis weitergeben
  });
}

// Verwendung
dateiLesen('datei.txt', (err, daten) => {
  if (err) {
    console.error('Fehler:', err);
    return;
  }
  console.log('Inhalt:', daten);
});

✅ Vorteile

  • Einfach zu verstehen (für kleine Aufgaben)
  • Keine zusätzlichen Bibliotheken nötig

❌ Nachteile

  • "Callback Hell" bei vielen verschachtelten Callbacks
  • Schwer zu lesen und zu warten

📚 Methode 2: Promises (Modern)

Promise = Ein Objekt, das einen zukünftigen Wert repräsentiert.

Promise-Zustände

Promise-Zustände:
┌─────────┐
│ Pending  │ ← Wartend (noch nicht fertig)
└─────────┘

┌─────────┐  oder  ┌─────────┐
│ Fulfilled│        │ Rejected│
│ (Erfolg) │        │(Fehler) │
└─────────┘        └─────────┘

Promise erstellen

javascript
// Promise erstellen
function dateiLesenAsync(pfad) {
  return new Promise((resolve, reject) => {
    fs.readFile(pfad, 'utf8', (err, daten) => {
      if (err) {
        reject(err);   // Fehler
      } else {
        resolve(daten); // Erfolg
      }
    });
  });
}

// Promise verwenden
dateiLesenAsync('datei.txt')
  .then(daten => {
    console.log('Erfolg:', daten);
  })
  .catch(err => {
    console.error('Fehler:', err);
  });

Promise-Kette (Chaining)

javascript
// Mehrere asynchrone Operationen nacheinander
dateiLesenAsync('datei1.txt')
  .then(daten1 => {
    console.log('Datei 1:', daten1);
    return dateiLesenAsync('datei2.txt');  // Nächstes Promise
  })
  .then(daten2 => {
    console.log('Datei 2:', daten2);
    return dateiLesenAsync('datei3.txt');
  })
  .then(daten3 => {
    console.log('Datei 3:', daten3);
  })
  .catch(err => {
    console.error('Fehler in einer der Dateien:', err);
  });

📚 Methode 3: async/await (Empfohlen!)

async/await = Die modernste und lesbarste Methode.

Grundlegende Syntax

javascript
// Funktion mit async markieren
async function meineFunktion() {
  try {
    // await = "Warte auf das Ergebnis"
    const daten = await dateiLesenAsync('datei.txt');
    console.log('Inhalt:', daten);
    
    // Weitere asynchrone Operationen
    const daten2 = await dateiLesenAsync('datei2.txt');
    console.log('Inhalt 2:', daten2);
    
  } catch (err) {
    console.error('Fehler:', err);
  }
}

// Funktion aufrufen
meineFunktion();

✅ Vorteile von async/await

  • Lesbar wie synchroner Code
  • Fehlerbehandlung mit try/catch
  • Kein Callback Hell
  • Einfach zu debuggen

6.3 Callback Hell

❌ Was ist "Callback Hell"?

javascript
// ❌ Callback Hell (Pyramid of Doom)
fs.readFile('datei1.txt', 'utf8', (err1, daten1) => {
  if (err1) {
    console.error(err1);
    return;
  }
  fs.readFile('datei2.txt', 'utf8', (err2, daten2) => {
    if (err2) {
      console.error(err2);
      return;
    }
    fs.readFile('datei3.txt', 'utf8', (err3, daten3) => {
      if (err3) {
        console.error(err3);
        return;
      }
      console.log('Alle Dateien gelesen:', daten1, daten2, daten3);
      // Noch mehr Callbacks... 😱
    });
  });
});

✅ Lösung 1: Promises verwenden

javascript
// ✅ Mit Promises (Flacher und lesbarer)
function leseDatei(pfad) {
  return new Promise((resolve, reject) => {
    fs.readFile(pfad, 'utf8', (err, daten) => {
      if (err) reject(err);
      else resolve(daten);
    });
  });
}

leseDatei('datei1.txt')
  .then(daten1 => {
    console.log('Datei 1:', daten1);
    return leseDatei('datei2.txt');
  })
  .then(daten2 => {
    console.log('Datei 2:', daten2);
    return leseDatei('datei3.txt');
  })
  .then(daten3 => {
    console.log('Datei 3:', daten3);
  })
  .catch(err => {
    console.error('Fehler:', err);
  });

✅ Lösung 2: async/await verwenden (Beste Lösung!)

javascript
// ✅✅ Mit async/await (Beste Lesbarkeit!)
async function alleDateienLesen() {
  try {
    const daten1 = await leseDatei('datei1.txt');
    console.log('Datei 1:', daten1);
    
    const daten2 = await leseDatei('datei2.txt');
    console.log('Datei 2:', daten2);
    
    const daten3 = await leseDatei('datei3.txt');
    console.log('Datei 3:', daten3);
    
    console.log('Alle Dateien erfolgreich gelesen!');
    
  } catch (err) {
    console.error('Fehler beim Lesen:', err);
  }
}

alleDateienLesen();

6.4 Praxisbeispiele

📂 Fallstudie 1: Dateien parallel lesen

javascript
const fs = require('fs').promises;  // Promise-basierte API

// Methode 1: Nacheinander lesen (Langsam)
async function nacheinander() {
  console.time('Nacheinander');
  
  const datei1 = await fs.readFile('datei1.txt', 'utf8');
  const datei2 = await fs.readFile('datei2.txt', 'utf8');
  const datei3 = await fs.readFile('datei3.txt', 'utf8');
  
  console.timeEnd('Nacheinander');
  return [datei1, datei2, datei3];
}

// Methode 2: Parallel lesen (Schnell!) ✅
async function parallel() {
  console.time('Parallel');
  
  const [datei1, datei2, datei3] = await Promise.all([
    fs.readFile('datei1.txt', 'utf8'),
    fs.readFile('datei2.txt', 'utf8'),
    fs.readFile('datei3.txt', 'utf8')
  ]);
  
  console.timeEnd('Parallel');
  return [datei1, datei2, datei3];
}

// Test
parallel();  // Viel schneller!

🌐 Fallstudie 2: HTTP-Request mit async/await

javascript
const https = require('https');

// Hilfsfunktion: HTTP-Request als Promise
function httpGet(url) {
  return new Promise((resolve, reject) => {
    https.get(url, (res) => {
      let daten = '';
      
      res.on('data', chunk => {
        daten += chunk;
      });
      
      res.on('end', () => {
        resolve(daten);
      });
      
    }).on('error', (err) => {
      reject(err);
    });
  });
}

// Async/Await verwenden
async function apiAufrufen() {
  try {
    console.log('Rufe API auf...');
    const ergebnis = await httpGet('https://api.github.com/users/github');
    const json = JSON.parse(ergebnis);
    
    console.log('API Ergebnis:');
    console.log('Name:', json.name);
    console.log('Followers:', json.followers);
    
  } catch (err) {
    console.error('Fehler beim API-Aufruf:', err.message);
  }
}

apiAufrufen();

6.5 Häufige Fehler

⚠️ Fehler 1: Vergessen von await

javascript
// ❌ Falsch: await vergessen
async function meinFehler() {
  const daten = dateiLesenAsync('datei.txt');  // Promise-Objekt, nicht der Inhalt!
  console.log(daten);  // ← Promise { <pending> }
}

// ✅ Richtig: await verwenden
async function korrekt() {
  const daten = await dateiLesenAsync('datei.txt');
  console.log(daten);  // ← Der tatsächliche Inhalt
}

⚠️ Fehler 2: await außerhalb von async-Funktion

javascript
// ❌ Falsch: await in normaler Funktion
function meineFunktion() {
  const daten = await dateiLesenAsync('datei.txt');  // SyntaxError!
}

// ✅ Richtig: Funktion muss async sein
async function meineFunktion() {
  const daten = await dateiLesenAsync('datei.txt');
}

⚠️ Fehler 3: Fehlerbehandlung fehlt

javascript
// ❌ Gefährlich: Keine Fehlerbehandlung
async function gefaehrlich() {
  const daten = await dateiLesenAsync('datei.txt');
  console.log(daten);  // Wenn Datei nicht existiert → Crash!
}

// ✅ Sicher: try/catch verwenden
async function sicher() {
  try {
    const daten = await dateiLesenAsync('datei.txt');
    console.log(daten);
  } catch (err) {
    console.error('Fehler:', err.message);
  }
}

⚠️ Fehler 4: await in Schleifen (Performance-Problem)

javascript
// ❌ Langsam: await in Schleife (Nacheinander)
async function langsam() {
  const ergebnisse = [];
  const urls = ['url1', 'url2', 'url3'];
  
  for (const url of urls) {
    const daten = await httpGet(url);  // Wartet jedes Mal!
    ergebnisse.push(daten);
  }
  
  return ergebnisse;
}

// ✅ Schnell: Promise.all verwenden (Parallel)
async function schnell() {
  const urls = ['url1', 'url2', 'url3'];
  
  const versprechen = urls.map(url => httpGet(url));
  const ergebnisse = await Promise.all(versprechen);  // Parallel!
  
  return ergebnisse;
}

📝 Zusammenfassung

In diesem Kapitel haben Sie gelernt:

  • ✅ Synchron = Blockierend, Asynchron = Nicht-blockierend
  • ✅ Callback-Funktionen (Traditionell, aber "Callback Hell")
  • ✅ Promises (Besser, mit .then() und .catch())
  • ✅ async/await (Am besten, lesbar wie synchroner Code)
  • ✅ "Callback Hell" vermeiden mit Promises oder async/await
  • ✅ Häufige Fehler: await vergessen, try/catch vergessen

🎯 Nächste Schritte

Im nächsten Kapitel werden wir:

  • HTTP-Server mit dem http-Modul erstellen
  • Anfragen (req) und Antworten (res) verarbeiten
  • Routing implementieren

📚 Weiterführende Ressourcen


🎉 Kapitel 6 abgeschlossen! Weiter zu Kapitel 7: HTTP-Server Entwicklung

Frei für alle Anfänger