Einführung in die asynchrone Programmierung in JavaScript
\n\nJavaScript ist eine Single-Thread-Programmiersprache. Das bedeutet, dass zu jedem Zeitpunkt nur eine Operation ausgeführt wird. Wenn jedoch alle Operationen sequenziell ausgeführt würden, würde der Browser beim Laden von Daten vom Server oder beim Lesen einer Datei "einfrieren". Genau hier kommt die asynchrone Programmierung ins Spiel.
\n\nAsynchrone Programmierung ermöglicht es, langwierige Operationen (API-Anfragen, Dateilesen, Timer) auszuführen, ohne den Hauptausführungs-Thread zu blockieren. Anstatt auf den Abschluss der Aufgabe zu warten, wird der Code weiter ausgeführt und das Ergebnis später verarbeitet. Dies ist ein zentrales Konzept für die Erstellung schneller und reaktionsfähiger Anwendungen mit Node.js.
\n\nIn diesem Leitfaden behandeln wir drei grundlegende Ansätze der asynchronen Programmierung: Callbacks, Promises und die async/await-Syntax. Sie erfahren, wie Sie korrekt mit asynchronem Code arbeiten und typische Anfängerfehler vermeiden.
1. Callbacks: Die Grundlage der Asynchronität
\n\nEin Callback ist eine Funktion, die als Argument an eine andere Funktion übergeben und nach Abschluss einer asynchronen Operation aufgerufen wird. Dies ist die älteste und grundlegendste Methode zur Arbeit mit Asynchronität in JavaScript.
\n\nBeispiel mit setTimeout
\n\nDie eingebaute Funktion setTimeout ist ein klassisches Beispiel für eine asynchrone Operation. Sie nimmt einen Callback und eine Verzögerung in Millisekunden entgegen:
console.log('Start');\n\nsetTimeout(() => {\n console.log('2 Sekunden vergangen');\n}, 2000);\n\nconsole.log('Ende');\n\n// Ausgabe:\n// Start\n// Ende\n// 2 Sekunden vergangen\n\nBeachten Sie: "Ende" wird vor der Nachricht aus dem Callback ausgegeben. Das ist Asynchronität – der Code wartet nicht auf den Abschluss von setTimeout.
Das Problem "Callback Hell"
\n\nWenn Sie viele verschachtelte asynchrone Operationen haben, wird der Code schwer lesbar. Dieses Phänomen wird als "Callback Hell" (Hölle der Callbacks) bezeichnet:
\n\nfs.readFile('datei1.txt', (err, data1) => {\n if (err) throw err;\n fs.readFile('datei2.txt', (err, data2) => {\n if (err) throw err;\n fs.readFile('datei3.txt', (err, data3) => {\n if (err) throw err;\n console.log(data1, data2, data3);\n });\n });\n});\n\nDieser Code ist schwer zu warten und zu debuggen. Glücklicherweise bietet modernes JavaScript elegantere Lösungen.
\n\n2. Promises: Elegante Handhabung von Asynchronität
\n\nEin Promise (Versprechen) ist ein Objekt, das das Ergebnis einer asynchronen Operation darstellt. Ein Promise kann sich in einem von drei Zuständen befinden:
\n\n- \n
- Pending (ausstehend) – Anfangszustand, die Operation ist noch nicht abgeschlossen \n
- Fulfilled (erfüllt) – die Operation wurde erfolgreich abgeschlossen \n
- Rejected (abgelehnt) – die Operation wurde mit einem Fehler abgeschlossen \n
Erstellung und Verwendung von Promises
\n\nconst myPromise = new Promise((resolve, reject) => {\n setTimeout(() => {\n const success = true;\n if (success) {\n resolve('Daten empfangen');\n } else {\n reject('Fehler beim Empfangen der Daten');\n }\n }, 2000);\n});\n\nmyPromise\n .then(result => {\n console.log(result); // "Daten empfangen"\n })\n .catch(error => {\n console.error(error); // "Fehler beim Empfangen der Daten"\n })\n .finally(() => {\n console.log('Operation abgeschlossen');\n });\n\nPromise-Verkettung (Chaining)
\n\nPromises können zu Ketten verbunden werden, was die "Callback Hell" vermeidet:
\n\nfunction readFile(filename) {\n return new Promise((resolve, reject) => {\n fs.readFile(filename, 'utf8', (err, data) => {\n if (err) reject(err);\n else resolve(data);\n });\n });\n}\n\nreadFile('datei1.txt')\n .then(data1 => {\