Wie kann ich in Node.js mit Promises arbeiten statt mit Callbacks?
- Einführung in das Problem der Callbacks
- Grundlagen von Promises in Node.js
- Callbacks durch Promises ersetzen
- 1. Verwendung der Promise-basierten API von Node.js
- 2. Promisify: Callback-Funktionen in Promises umwandeln
- 3. Eigene Promises aus Callback-Funktionen erstellen
- Vorteile der Nutzung von Promises
- Zusammenfassung
Einführung in das Problem der Callbacks
Node.js nutzte von Anfang an ein asynchrones Design, um die Performance bei Ein- und Ausgabeoperationen zu optimieren. Die klassische Art, mit asynchronem Code umzugehen, sind sogenannte Callbacks. Dabei wird eine Funktion als Argument übergeben, die nach Abschluss einer asynchronen Operation aufgerufen wird. Dies führt jedoch häufig zu sogenannten "Callback-Höllen" oder verschachteltem Code, der schwer lesbar und fehleranfällig ist.
Um dieses Problem zu adressieren, wurden Promises als moderne Alternative eingeführt. Promises vereinfachen den Umgang mit asynchronem Code, indem sie ein Objekt zurückgeben, das den zukünftigen Wert einer Operation symbolisiert. Damit kann man asynchronen Code linearer und klarer schreiben.
Grundlagen von Promises in Node.js
Ein Promise ist ein Objekt, das einen zukünftigen Wert repräsentiert, der entweder erfolgreich (fulfilled) oder mit einem Fehler (rejected) abgeschlossen wird. Der grundlegende Unterschied zu Callbacks ist, dass Promises eine klar definierte Schnittstelle mit Methoden wie .then(), .catch() und .finally() bieten, um die Verarbeitung der Ergebnisse zu regeln.
Hier ist ein einfaches Beispiel, wie ein Promise manuell erstellt wird:
const myPromise = new Promise((resolve, reject) => { // Simuliere eine asynchrone Operation, z.B. eine Datenbankabfrage setTimeout(() => { const success = true; if (success) { resolve("Erfolg!"); } else { reject("Fehler!"); } }, 1000);});myPromise .then(result => { console.log("Ergebnis:", result); }) .catch(error => { console.error("Fehler:", error); });Callbacks durch Promises ersetzen
In Node.js-Funktionen, die traditionelle Callbacks verwenden (oft in der Form function(arg1, arg2, callback)), kann man Promises verwenden, indem man entweder selbst eine Promise um die Callback-basierte Funktion wickelt oder auf eingebaute Promise-Varianten und Utility-Funktionen zurückgreift.
Ein klassisches Beispiel ist das Einlesen einer Datei mit fs.readFile, das ursprünglich ein Callback erwartet:
const fs = require(fs);fs.readFile(datei.txt, utf8, (err, data) => { if (err) { console.error("Fehler:", err); return; } console.log("Dateiinhalt:", data);});Um dies mit Promises zu lösen, gibt es verschiedene Optionen.
1. Verwendung der Promise-basierten API von Node.js
Ab Node.js Version 10 gibt es das Modul fs/promises, das die gleichen Funktionen, aber bereits als Promises implementiert anbietet:
const fs = require(fs/promises);fs.readFile(datei.txt, utf8) .then(data => { console.log("Dateiinhalt:", data); }) .catch(err => { console.error("Fehler:", err); });Alternativ kann man async/await verwenden, was den Code sogar noch lesbarer macht:
const fs = require(fs/promises);async function leseDatei() { try { const data = await fs.readFile(datei.txt, utf8); console.log("Dateiinhalt:", data); } catch (err) { console.error("Fehler:", err); }}leseDatei();2. Promisify: Callback-Funktionen in Promises umwandeln
Node.js bietet mit util.promisify ein Mittel, um existierende callback-basierte Funktionen in Promise-basierte Funktionen zu konvertieren, ohne die ursprüngliche Funktion umbauen zu müssen.
const fs = require(fs);const util = require(util);const readFilePromise = util.promisify(fs.readFile);readFilePromise(datei.txt, utf8) .then(data => { console.log("Dateiinhalt:", data); }) .catch(err => { console.error("Fehler:", err); });Dieses Verfahren ist sehr nützlich, wenn man ältere Node.js-APIs oder eigene Callback-Funktionen mit Promises nutzen möchte.
3. Eigene Promises aus Callback-Funktionen erstellen
Wenn du eine eigene Funktion hast, die mit Callbacks arbeitet, kannst du sie manuell in einen Promise-Wrapping-Code einschließen. Angenommen du hast eine Funktion wie:
function ladeDaten(callback) { setTimeout(() => { const daten = "Hier sind die Daten"; if (!daten) { callback(new Error("Keine Daten")); } else { callback(null, daten); } }, 1000);}Dann kannst du daraus eine Promise-basierte Funktion machen:
function ladeDatenPromise() { return new Promise((resolve, reject) => { ladeDaten((err, daten) => { if (err) { reject(err); } else { resolve(daten); } }); });}ladeDatenPromise() .then(daten => { console.log("Erhaltene Daten:", daten); }) .catch(err => { console.error("Fehler:", err); });Vorteile der Nutzung von Promises
Promises bieten viele Vorteile gegenüber reinen Callbacks. Zum einen lassen sie dich Fehler zentral und konsistent über .catch() oder mit try/catch in async/await Blocken behandeln. Außerdem kannst du mit Promise-Ketten verschiedene asynchrone Operationen hintereinander oder parallel verarbeiten und die Lesbarkeit erheblich verbessern. Zudem erlaubt die Kombination mit async und await, asynchronen Code fast so einfach wie synchronen Code zu schreiben, was die Wartbarkeit steigert.
Zusammenfassung
Der Wechsel von Callbacks zu Promises in Node.js bedeutet, asynchrone Operationen klarer, besser lesbar und wartbar zu gestalten. Node.js stellt dafür moderne APIs (z.B. fs/promises), Werkzeuge wie util.promisify und die Möglichkeit, selbst Promises zu erstellen, bereit. Besonders in Kombination mit async/await wird asynchroner Code in Node.js so zu einem übersichtlichen und eleganten Bestandteil deiner Anwendungen.
