Asynchrone Operationen in Node.js
Callbacks, Promises und async/await: Lerne, wie Node.js asynchrone Operationen handhabt und wie du sauberen, nicht-blockierenden Code schreibst.
Node.js ist von Grund auf für asynchrone Arbeit gebaut. Dateien lesen, Datenbanken abfragen oder HTTP-Anfragen senden, all das passiert nicht-blockierend, damit dein Programm währenddessen weiterarbeiten kann. In diesem Beitrag lernst du die drei Generationen asynchroner Programmierung kennen: Callbacks, Promises und das moderne async/await.
Warum überhaupt asynchron?
Node.js läuft in einem einzigen Thread. Würde jede Datei- oder Netzwerkoperation warten, bis sie fertig ist, stünde dein ganzes Programm still. Stattdessen startet Node die Operation, macht weiter und kümmert sich um das Ergebnis, sobald es eintrifft. Dieses Prinzip nennt man nicht-blockierend.
console.log('Erstes');
setTimeout(() => {
console.log('Zweites (nach 1 Sekunde)');
}, 1000);
console.log('Drittes');
// Ausgabe: Erstes, Drittes, ZweitesBeachte, dass "Drittes" vor "Zweites" erscheint, weil setTimeout die Ausführung verzögert, ohne den Rest zu blockieren.
Die Callback-Ära
Früher übergab man eine Funktion, die nach Abschluss aufgerufen wird, einen sogenannten Callback. Per Konvention ist der erste Parameter immer ein möglicher Fehler.
const fs = require('fs');
fs.readFile('daten.txt', 'utf8', (err, inhalt) => {
if (err) {
console.error('Fehler beim Lesen:', err);
return;
}
console.log('Inhalt:', inhalt);
});Das Problem: Verschachtelst du viele Callbacks ineinander, entsteht die berüchtigte "Callback-Hölle", die schwer zu lesen und zu warten ist.
Promises als Verbesserung
Ein Promise ist ein Objekt, das einen Wert repräsentiert, der erst später verfügbar wird. Es kann sich erfüllen (resolve) oder fehlschlagen (reject). Mit .then() und .catch() verkettest du die Schritte sauber.
const fs = require('fs').promises;
fs.readFile('daten.txt', 'utf8')
.then((inhalt) => {
console.log('Inhalt:', inhalt);
return inhalt.toUpperCase();
})
.then((gross) => {
console.log('Großbuchstaben:', gross);
})
.catch((err) => {
console.error('Fehler:', err);
});Anders als bei verschachtelten Callbacks bleibt die Kette flach und gut lesbar.
async/await: der moderne Weg
Mit async/await sieht asynchroner Code fast wie synchroner aus. Du markierst eine Funktion mit async und wartest mit await auf das Ergebnis eines Promises.
const fs = require('fs').promises;
async function leseDatei() {
try {
const inhalt = await fs.readFile('daten.txt', 'utf8');
console.log('Inhalt:', inhalt);
const gross = inhalt.toUpperCase();
console.log('Großbuchstaben:', gross);
} catch (err) {
console.error('Fehler:', err);
}
}
leseDatei();Die Fehlerbehandlung erledigst du hier mit dem vertrauten try/catch-Block, was den Code besonders übersichtlich macht.
Mehrere Operationen parallel ausführen
Wenn mehrere unabhängige Operationen gleichzeitig laufen können, solltest du sie nicht nacheinander abwarten. Mit Promise.all startest du sie parallel und wartest nur einmal auf alle.
const fs = require('fs').promises;
async function leseMehrere() {
// Beide Lesevorgänge starten gleichzeitig
const [a, b] = await Promise.all([
fs.readFile('a.txt', 'utf8'),
fs.readFile('b.txt', 'utf8'),
]);
console.log('Datei A:', a);
console.log('Datei B:', b);
}
leseMehrere();Das ist deutlich schneller, als jede Datei einzeln mit await nacheinander zu laden.
Fazit
Asynchrone Operationen sind das Herzstück von Node.js. Callbacks waren der Anfang, Promises brachten Struktur, und mit async/await schreibst du heute lesbaren, nicht-blockierenden Code. Nutze try/catch für die Fehlerbehandlung und Promise.all, wenn mehrere Aufgaben parallel laufen können. Wenn du diese Konzepte beherrschst, hast du den wichtigsten Baustein der Node.js-Programmierung verstanden.