Umgang mit der Rückrufpyramide von node.js.

9

Ich habe gerade angefangen, Node zu verwenden, und eine Sache, die mir schnell aufgefallen ist, ist, wie schnell sich Rückrufe zu einem albernen Einrückungsgrad aufbauen können:

doStuff(arg1, arg2, function(err, result) {
    doMoreStuff(arg3, arg4, function(err, result) {
        doEvenMoreStuff(arg5, arg6, function(err, result) {
            omgHowDidIGetHere();
        });
    });
});

Der offizielle Styleguide besagt, dass jeder Rückruf in eine separate Funktion gestellt werden soll. Dies scheint jedoch die Verwendung von Verschlüssen zu stark einzuschränken und ein einzelnes Objekt, das in der obersten Ebene deklariert ist, mehrere Ebenen tiefer verfügbar zu machen, da das Objekt alle durchlaufen muss Zwischenrückrufe.

Ist es in Ordnung, den Funktionsumfang zu verwenden, um hier zu helfen? Fügen Sie alle Rückruffunktionen, die Zugriff auf ein globales Objekt benötigen, in eine Funktion ein, die dieses Objekt deklariert, damit es geschlossen wird.

function topLevelFunction(globalishObject, callback) {

    function doMoreStuffImpl(err, result) {
        doMoreStuff(arg5, arg6, function(err, result) {
            callback(null, globalishObject);
        });
    }

    doStuff(arg1, arg2, doMoreStuffImpl);
}

und so weiter für mehrere weitere Schichten ...

Oder gibt es Frameworks usw., mit denen die Einrückungsstufen reduziert werden können, ohne für jeden einzelnen Rückruf eine benannte Funktion zu deklarieren? Wie gehen Sie mit der Rückrufpyramide um?

thecoop
quelle
2
Beachten Sie das zusätzliche Problem mit den Schließungen - in JS erfasst die Schließung den gesamten übergeordneten Kontext (in anderen Sprachen werden nur die verwendeten Variablen oder die vom Benutzer speziell angeforderten Variablen erfasst), was zu netten Speicherverlusten führt, wenn die Rückrufhierarchie tief genug ist und wenn beispielsweise Rückruf wird irgendwo beibehalten.
Eugene

Antworten:

7

Versprechen bieten eine saubere Trennung von Bedenken zwischen asynchronem Verhalten und der Schnittstelle, sodass asynchrone Funktionen ohne Rückrufe aufgerufen werden können und die Rückrufinteraktion über die generische Versprechen-Schnittstelle durchgeführt werden kann.

Es gibt mehrere Implementierungen von "Versprechungen":


Beispielsweise können Sie diese verschachtelten Rückrufe neu schreiben

http.get(url.parse("http://test.com/"), function(res) {
    console.log(res.statusCode);
    http.get(url.parse(res.headers["location"]), function(res) {
        console.log(res.statusCode);
    });
});

mögen

httpGet(url.parse("http://test.com/")).then(function (res) {
    console.log(res.statusCode);
    return httpGet(url.parse(res.headers["location"]));
}).then(function (res) {
    console.log(res.statusCode);
});

Anstelle eines Rückrufs im Rückruf a(b(c()))verketten Sie das ".then" a().then(b()).then(c()).


Eine Einführung hier: http://howtonode.org/promises

Fabien Sa.
quelle
Würde es Ihnen etwas ausmachen, mehr darüber zu erklären, was diese Ressourcen bewirken, und warum empfehlen Sie diese als Antwort auf die gestellte Frage? "Nur-Link-Antworten" sind bei Stack Exchange nicht ganz willkommen
Mücke
1
Ok, tut mir Leid. Ich habe ein Beispiel und weitere Informationen hinzugefügt.
Fabien Sa
3

Als Alternative zu Versprechungen sollten Sie sich das yieldSchlüsselwort in Kombination mit Generatorfunktionen ansehen, die in EcmaScript 6 eingeführt werden. Beide sind heute in Node.js 0.11.x-Builds verfügbar, erfordern jedoch, dass Sie --harmonybeim Ausführen von Node zusätzlich das Flag angeben .js:

$ node --harmony app.js

Wenn Sie diese Konstrukte und eine Bibliothek wie TJ Holowaychuks co verwenden , können Sie asynchronen Code in einem Stil schreiben, der wie synchroner Code aussieht , obwohl er immer noch asynchron ausgeführt wird. Grundsätzlich implementieren diese Dinge zusammen die Co-Routine-Unterstützung für Node.js.

Grundsätzlich müssen Sie eine Generatorfunktion für den Code schreiben, der asynchrones Material ausführt, das dortige asynchrone Material aufrufen, ihm jedoch das yieldSchlüsselwort voranstellen . Am Ende sieht Ihr Code also so aus:

var run = function * () {
  var data = yield doSomethingAsync();
  console.log(data);
};

Um diese Generatorfunktion auszuführen, benötigen Sie eine Bibliothek wie die zuvor erwähnte co. Der Anruf sieht dann so aus:

co(run);

Oder, um es inline zu setzen:

co(function * () {
  // ...
});

Bitte beachten Sie, dass Sie von den Generatorfunktionen aus andere Generatorfunktionen aufrufen können. Sie müssen ihnen lediglich yielderneut ein Präfix voranstellen .

Um eine Einführung in dieses Thema zu erhalten, durchsuchen Sie Google nach Begriffen wie yield generators es6 async nodejsund Sie sollten unzählige Informationen finden. Es dauert eine Weile, bis man sich daran gewöhnt hat, aber wenn man es einmal hat, will man nie mehr zurück.

Bitte beachten Sie, dass Sie dadurch nicht nur eine bessere Syntax zum Aufrufen von Funktionen erhalten, sondern auch die üblichen (synchronen) Steuerflusslogikfunktionen wie forSchleifen oder try/ verwenden können catch. Kein Herumspielen mehr mit vielen Rückrufen und all diesen Dingen.

Viel Glück und hab Spaß :-)!

Golo Roden
quelle
0

Sie haben jetzt das Paket asyncawait mit einer sehr engen Syntax für die zukünftige native Unterstützung von await& asyncin Node.

Grundsätzlich können Sie asynchronen Code schreiben, der synchron aussieht, wodurch die LOC- und Einrückungsstufen drastisch reduziert werden, wobei ein geringfügiger Leistungsverlust zu erwarten ist (die Anzahl der Paketinhaber beträgt 79% Geschwindigkeit im Vergleich zu unformatierten Rückrufen), der hoffentlich reduziert wird, wenn native Unterstützung verfügbar ist.

Immer noch eine gute Wahl, um aus der Callback-Hölle / Pyramide des Schicksals-Alptraums IMO herauszukommen, wenn Leistung nicht das Hauptanliegen ist und der synchrone Schreibstil besser zu den Anforderungen Ihres Projekts passt.

Grundlegendes Beispiel aus dem Paket doc:

var foo = async (function() {
    var resultA = await (firstAsyncCall());
    var resultB = await (secondAsyncCallUsing(resultA));
    var resultC = await (thirdAsyncCallUsing(resultB));
    return doSomethingWith(resultC);
});
Frosty Z.
quelle