Einführung in die asynchrone Programmierung in JavaScript
JavaScript ist eine single-threaded Programmiersprache mit einem nicht-blockierenden I/O-Modell. Das bedeutet, dass zu einem bestimmten Zeitpunkt nur eine Operation ausgeführt wird. Dank der asynchronen Programmierung kann Node.js jedoch Tausende von Anfragen gleichzeitig verarbeiten, ohne den Hauptthread zu blockieren. In diesem Artikel werden wir die grundlegenden Konzepte der Asynchronität behandeln: Callbacks, Promises und die async/await-Syntax.
Was ist Asynchronität und warum wird sie benötigt?
Stellen Sie sich vor, Sie bestellen eine Pizza. Wenn Sie an der Theke stehen und warten, bis sie zubereitet ist, handelt es sich um synchronen Code. Alles andere wird gestoppt. Der asynchrone Ansatz – Sie geben die Bestellung auf und kümmern sich um Ihre eigenen Angelegenheiten. Wenn die Pizza fertig ist, werden Sie gerufen. JavaScript funktioniert genau so: Es wartet nicht auf den Abschluss einer Operation (Lesen einer Datei, Anfrage an einen Server), sondern führt den nächsten Code weiter aus. Wenn die Operation abgeschlossen ist, wird eine spezielle Funktion aufgerufen – ein Callback.
In Node.js ist Asynchronität besonders wichtig, da der Server viele Clients gleichzeitig bedienen muss. Ohne sie würde jede neue Anfrage die gesamte Anwendung blockieren.
Synchroner vs. Asynchroner Code: Beispiel
Betrachten wir ein einfaches Beispiel zum Lesen einer Datei. Die synchrone Variante blockiert die Ausführung:
const fs = require('fs');
// Synchrones Lesen – blockiert den Threadconst data = fs.readFileSync('file.txt', 'utf8');console.log('Daten gelesen:', data);console.log('Dieser Code wird erst nach dem Lesen der Datei ausgeführt');Die asynchrone Variante blockiert nicht:
const fs = require('fs');
// Asynchrones Lesen – blockiert nichtfs.readFile('file.txt', 'utf8', (err, data) => { if (err) throw err; console.log('Daten gelesen:', data);});
console.log('Dieser Code wird sofort ausgeführt, ohne auf das Lesen der Datei zu warten');Grundlegende Methoden der Arbeit mit Asynchronität
In JavaScript (und Node.js) gibt es drei Hauptansätze für die asynchrone Programmierung. Wir werden sie von den ältesten bis zu den modernsten betrachten.
1. Callback (Rückruffunktionen)
Ein Callback ist eine Funktion, die an eine andere Funktion übergeben und nach Abschluss einer asynchronen Operation aufgerufen wird. Dies ist die grundlegendste und älteste Methode.
function getUserData(userId, callback) { setTimeout(() => { const user = { id: userId, name: 'Anna' }; callback(null, user); // erstes Argument – Fehler (falls vorhanden) }, 1000);}
getUserData(1, (err, user) => { if (err) { console.error('Fehler:', err); return; } console.log('Benutzer:', user.name);});Problem: Wenn es viele Operationen gibt, verwandelt sich der Code in die "Callback-Hölle" (callback hell) – tief verschachtelte Funktionen, die schwer zu lesen und zu debuggen sind.
2. Promise (Promises)
Ein Promise ist ein Objekt, das das Ergebnis einer asynchronen Operation darstellt. Es kann sich in einem von drei Zuständen befinden:
- pending (ausstehend) – die Operation ist noch nicht abgeschlossen
- fulfilled (erfüllt) – die Operation wurde erfolgreich abgeschlossen
- rejected (abgelehnt) – die Operation wurde mit einem Fehler abgeschlossen
Promises lösen das Problem der Callback-Hölle und machen den Code linearer:
function getUserData(userId) { return new Promise((resolve, reject) => { setTimeout(() => { const user = { id: userId, name: 'Ivan' }; // resolve – Erfolg, reject – Fehler resolve(user); // reject(new Error('Benutzer nicht gefunden')); }, 1000); });}
// Verwendung der .then()-KettegetUserData(1) .then(user => { console.log('Benutzer:', user.name); return user.id; }) .then(id => console.log('ID:', id)) .catch(err => console.error('Fehler:', err)) .finally(() => console.log('Operation abgeschlossen'));