Callbacks + Promises: Es ist kompliziert

Dank async und await erleben Promises in der JavaScript-Welt derzeit einen neuen Aufschwung. Zahlreiche Module wollen von Callbacks auf die neue API umgestellt werden. Während des Übergangs gilt es jedoch einige Fallstricke zu vermeiden.

In Pocket speichern vorlesen Druckansicht
Lesezeit: 2 Min.
Von
  • Golo Roden

Dank async und await erleben Promises in der JavaScript-Welt derzeit einen neuen Aufschwung. Zahlreiche Module wollen von Callbacks auf die neue API umgestellt werden. Während des Übergangs gilt es jedoch einige Fallstricke zu vermeiden.

Die gängigen Beispiele lesen sich leicht. Wer eine Callback-basierte API auf Promises umstellen will, dem stehen kaum Hindernisse im Weg. Verallgemeinert lässt sich sagen, dass Callback-basierter Code der Form

const fn = function (options, callback) {
// ...

if (err) {
return callback(err);
}

callback(null, result);
};

wie folgt umgeschrieben werden muss:

const fn = function (options) {
return new Promise((resolve, reject) => {
// ...

if (err) {
return reject(err);
}

resolve(result);
});
};

Worauf in den gängigen Beispielen wenig Wert gelegt wird, ist hingegen der umgekehrte Weg, also von Promises hin zu Callbacks. Das mag auf den ersten Blick sinnlos erscheinen, tritt in der Praxis aber häufig auf.

Das gilt beispielsweise immer dann, wenn die Schnittstelle eines bestehenden Moduls nicht verändert werden darf, man es intern aber bereits auf Promises umstellen will. Das führt dann häufig zu Code der folgenden Form:

const fn = function (options, callback) {
getFoo(options).
then(result => {
callback(null, result);
}).
catch(err => {
callback(err);
});
};

Das Beispiel sieht einfach und logisch aus, verursacht in der Praxis aber unter Umständen Probleme. Tritt nämlich innerhalb des Callbacks eine Ausnahme auf, wird diese im Kontext des then- beziehungsweise des catch-Blocks behandelt. Das entspricht selten dem, was man erwartet, außerdem erschwert es die Fehlersuche häufig deutlich.

Um das Problem zu lösen, müssen die beiden Aufrufe des Callbacks aus dem Kontext des Promises herausgelöst werden. Das erfolgt am einfachsten über die Funktion process.nextTick, wie das folgende Beispiel zeigt:

const fn = function (options, callback) {
getFoo(options).
then(result => {
process.nextTick(() => callback(null, result);
}).
catch(err => {
process.nextTick(() => callback(err));
});
};

tl;dr: Callbacks und Promises zu verbinden ist nicht ganz so einfach, wie es auf den ersten Blick zu sein scheint. Die Funktion process.nextTick hilft, die beiden Welten zu entkoppeln. ()