W3docs

JavaScript Promisification

JavaScript Promisification: Callbacks in Promises umwandeln, einen generischen promisify()-Helfer erstellen, Mehrfarg-Callbacks behandeln und Grenzen kennen.

Was ist Promisification?

Promisification bezeichnet das Umhüllen einer Callback-basierten Funktion, sodass sie statt eines Callbacks ein Promise zurückgibt. Man macht es einmal, und kann danach .then(), .catch(), Chaining und async/await auf einer Funktion verwenden, die ursprünglich nicht dafür konzipiert war.

Diese Seite erklärt, wie man eine einzelne Error-First-Callback-API einwickelt, wie man einen wiederverwendbaren generischen promisify()-Helfer erstellt, wie man Callbacks mit mehr als einem Ergebnis behandelt und in welchen Fällen Promisification nicht funktioniert.

Warum Promisify?

Ältere JavaScript-APIs und der Großteil der Node.js-Standardbibliothek liefern ihre Ergebnisse über einen übergebenen Callback. Dieser Stil verschachtelt sich schnell und verstreut die Fehlerbehandlung:

getUser(id, (err, user) => {
  if (err) return handleError(err);
  getOrders(user, (err, orders) => {
    if (err) return handleError(err);
    getTotal(orders, (err, total) => {
      if (err) return handleError(err);
      console.log(total);
    });
  });
});

Wenn dieselben Funktionen Promises zurückgäben, würde die Logik zu einer einzigen linearen Kette (oder einigen await-Zeilen) mit einem einzigen .catch() für den gesamten Ablauf flach. Promisification ist die Brücke zwischen diesen beiden Welten — siehe Callbacks and Beyond für die Callback-Seite der Geschichte.

Die Error-First-Callback-Konvention

Bevor man etwas einwickelt, muss man die Form kennen, die man einwickelt. Node.js-Callbacks folgen der Error-First- (oder „Node-Style"-)Konvention: Der Callback ist das letzte Argument und wird als callback(error, result) aufgerufen.

  • Bei einem Fehler ist error ein Error-Objekt und result ist undefined.
  • Bei Erfolg ist error null und result enthält den Wert.

Promisification bildet dies direkt ab: Ein nicht-null-Fehler wird zu reject(error), und ein erfolgreiches result wird zu resolve(result).

Eine einzelne Callback-API einwickeln

Hier ist das grundlegende Muster. Wir wickeln eine Callback-basierte Funktion in ein neues Promise ein, rufen reject für den Fehler und resolve für den Wert auf. Das Beispiel simuliert eine Error-First-API mit setTimeout, sodass es überall läuft, auch im Browser:


javascript— editable

Der Wrapper nimmt dasselbe id-Argument entgegen, leitet es weiter und stellt seinen eigenen Callback bereit, der in resolve/reject überbrückt. Der Aufrufer sieht keinen Callback mehr.

Den Wrapper mit async/await verwenden

Der eigentliche Vorteil einer Promise-zurückgebenden Funktion ist, dass sie mit async/await funktioniert und asynchronen Code in etwas verwandelt, das von oben nach unten gelesen wird:


javascript— editable

Ein generischer promisify()-Helfer

Für jede Funktion von Hand einen Wrapper zu schreiben wird schnell repetitiv. Ein generischer Helfer nimmt eine beliebige Error-First-Funktion entgegen und gibt eine Promise-zurückgebende Version davon zurück. Der Trick besteht darin, alle ursprünglichen Argumente mit einem Rest-Parameter zu sammeln und dann unseren eigenen Callback anzuhängen:


javascript— editable

Da der Helfer ...args verwendet und this weiterleitet, funktioniert er für Funktionen mit beliebig vielen führenden Argumenten. In Node.js wird genau das als util.promisify in der Standardbibliothek mitgeliefert, sodass man dort selten einen eigenen schreiben muss:

const fs = require('fs');
const util = require('util');

const readFile = util.promisify(fs.readFile);

readFile('file.txt', 'utf8')
  .then(data => console.log(data))
  .catch(err => console.error(err));

Callbacks mit mehreren Argumenten behandeln

Der einfache Helfer geht davon aus, dass der Callback ein einzelnes Ergebnis liefert: callback(err, result). Einige APIs übergeben mehrere Werte, z. B. callback(err, header, body). Ein einfaches resolve(result) würde alles nach dem ersten Wert stillschweigend verwerfen.

Ein Promise kann sich nur mit einem Wert auflösen, also sammelt man die zusätzlichen Argumente in einem array (oder einem object) und löst damit auf:


javascript— editable

Node's util.promisify unterstützt dieselbe Idee über ein benutzerdefiniertes Symbol (util.promisify.custom), aber für ad-hoc-Funktionen ist ein array der einfachste Ansatz.

Einschränkungen und Fallstricke

Promisification ist mechanisch, hat aber echte Grenzen:

  • Sie erwartet die Error-First-Konvention. Wenn eine Funktion Fehler auf andere Weise signalisiert — z. B. durch einen boolean-Rückgabewert, eine geworfene Ausnahme oder die Reihenfolge (result, err) — wird ein generischer Helfer sie falsch interpretieren. Solche Funktionen muss man von Hand einwickeln.
  • Sie behandelt nur eine einzige Fertigstellung. Promises werden einmalig erfüllt. Eine Funktion, die ihren Callback wiederholt aufruft (Events, Streams, setInterval, ein Fortschritts-Callback), kann nicht promisifiziert werden — nur der erste Aufruf würde das Promise auflösen; spätere Aufrufe werden ignoriert. Für wiederholende Werte verwendet man eine Event-API oder einen Async Iterator.
  • Ein Promise kann nicht abgebrochen werden. Wenn die zugrunde liegende Callback-API Abbruch unterstützt (z. B. das Löschen eines Timers), geht diese Fähigkeit verloren, sobald sie hinter einem Promise verborgen ist.
  • Der Wrapper ändert die Aufruf-Signatur. Aufrufer müssen nun .then/await verwenden, anstatt einen Callback zu übergeben. Man sollte eine Funktion nicht promisifizieren, die noch von anderem Code im Callback-Stil aufgerufen wird, ohne beide Versionen beizubehalten.
  • Ein throw innerhalb des Executors führt weiterhin zur Ablehnung. Code, der synchron innerhalb von new Promise((resolve, reject) => { ... }) ausgeführt wird, wird abgefangen und in eine Ablehnung umgewandelt — aber ein Fehler, der später innerhalb eines asynchronen Callbacks geworfen wird, wird nicht automatisch abgefangen. Genau deshalb muss reject(err) explizit aufgerufen werden.

Best Practices

  • Promisifiziere an der Grenze. Konvertiere I/O- und Timer-APIs einmal, nahe dem Punkt, an dem sie in deinen Code eintreten, und halte den Rest deiner Codebasis Promise-basiert.
  • Bevorzuge eingebaute Lösungen. In Node.js greife auf util.promisify (oder die Module fs/promises, dns/promises usw.) zurück, bevor du einen Wrapper von Hand schreibst.
  • Behandle Ablehnungen immer. Hänge ein .catch() an oder wickle await in try/catch ein; eine unbehandelte Ablehnung kann einen Node-Prozess zum Absturz bringen.
  • Halte Namen vorhersehbar. Eine verbreitete Konvention ist, die Promise-Version mit dem Suffix Async zu versehen (readFileAsync), damit beide Stile koexistieren können.

Verwandte Themen

  • JavaScript Promise — das Objekt, das du bei der Promisifizierung erstellst.
  • Promises Chaining — promisifizierte Aufrufe sauber verketten.
  • Async/Await — die Syntax, die promisifizierte Funktionen wie synchronen Code aussehen lässt.
  • Callbacks and Beyond — das Muster, von dem du konvertierst.
  • Promise API — mehrere promisifizierte Aufrufe mit Promise.all und Verwandten kombinieren.

Übungen

Übung
Was ist die Hauptfunktion der Promisification in JavaScript?
Was ist die Hauptfunktion der Promisification in JavaScript?
Was this page helpful?