So vermeiden Sie eine lange Verschachtelung asynchroner Funktionen in Node.js.


158

Ich möchte eine Seite erstellen, auf der einige Daten aus einer Datenbank angezeigt werden. Daher habe ich einige Funktionen erstellt, die diese Daten aus meiner Datenbank abrufen. Ich bin nur ein Neuling in Node.js. Soweit ich weiß, müsste ich sie alle verschachteln, wenn ich sie alle auf einer einzigen Seite verwenden möchte (HTTP-Antwort):

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  getSomeDate(client, function(someData) {
    html += "<p>"+ someData +"</p>";
    getSomeOtherDate(client, function(someOtherData) {
      html += "<p>"+ someOtherData +"</p>";
      getMoreData(client, function(moreData) {
        html += "<p>"+ moreData +"</p>";
        res.write(html);
        res.end();
      });
    });
  });

Wenn es viele solche Funktionen gibt, wird die Verschachtelung zu einem Problem .

Gibt es eine Möglichkeit, dies zu vermeiden? Ich denke, es hat damit zu tun, wie Sie mehrere asynchrone Funktionen kombinieren, was etwas Grundlegendes zu sein scheint.


12
Wenn Sie also 10 asynchrone Funktionen haben, haben Sie 10 Einrückungsstufen?
Kay Pale


1
Ein weiteres Problem: Das Einfügen einer anderen Funktion zwischen getSomeDateund führt dazu getSomeOtherDate, dass der Einzug vieler Zeilen geändert wird, wodurch das Lesen des Git-Verlaufs schwieriger wird ( git blamewas danach sogar nutzlos ist), und Sie machen wahrscheinlich Fehler, wenn Sie dies manuell tun
Daniel Alder

Antworten:


73

Interessante Beobachtung. Beachten Sie, dass Sie in JavaScript normalerweise anonyme Inline-Rückruffunktionen durch benannte Funktionsvariablen ersetzen können.

Folgende:

http.createServer(function (req, res) {
   // inline callback function ...

   getSomeData(client, function (someData) {
      // another inline callback function ...

      getMoreData(client, function(moreData) {
         // one more inline callback function ...
      });
   });

   // etc ...
});

Könnte umgeschrieben werden, um ungefähr so ​​auszusehen:

var moreDataParser = function (moreData) {
   // date parsing logic
};

var someDataParser = function (someData) {
   // some data parsing logic

   getMoreData(client, moreDataParser);
};

var createServerCallback = function (req, res) {
   // create server logic

   getSomeData(client, someDataParser);

   // etc ...
};

http.createServer(createServerCallback);

Wenn Sie jedoch nicht vorhaben, die Rückruflogik an anderen Stellen wiederzuverwenden, ist es häufig viel einfacher, anonyme Inline-Funktionen zu lesen, wie in Ihrem Beispiel. Sie müssen auch keinen Namen für alle Rückrufe finden.

Beachten Sie außerdem, dass, wie @pst in einem Kommentar unten vermerkt, wenn Sie auf Schließungsvariablen innerhalb der inneren Funktionen zugreifen, dies keine einfache Übersetzung ist. In solchen Fällen ist die Verwendung anonymer Inline-Funktionen noch vorzuziehen.


26
Allerdings (und wirklich nur den Kompromiss zu verstehen) , wenn nicht-verschachtelt, einige Schließung Semantik über Variablen können verloren gehen, so dass es keine direkte Übersetzung. Im obigen Beispiel geht der Zugriff auf 'res' in getMoreDataverloren.

2
Ich denke, Ihre Lösung ist kaputt: someDataParseranalysiert tatsächlich ALLE Daten, da sie auch aufruft getMoreData. In diesem Sinne ist der Funktionsname falsch und es wird deutlich, dass wir das Verschachtelungsproblem nicht tatsächlich entfernt haben.
Konstantin Schubert

63

Kay, benutze einfach eines dieser Module.

Es wird dies ändern:

dbGet('userIdOf:bobvance', function(userId) {
    dbSet('user:' + userId + ':email', 'bobvance@potato.egg', function() {
        dbSet('user:' + userId + ':firstName', 'Bob', function() {
            dbSet('user:' + userId + ':lastName', 'Vance', function() {
                okWeAreDone();
            });
        });
    });
});

Das mögen:

flow.exec(
    function() {
        dbGet('userIdOf:bobvance', this);

    },function(userId) {
        dbSet('user:' + userId + ':email', 'bobvance@potato.egg', this.MULTI());
        dbSet('user:' + userId + ':firstName', 'Bob', this.MULTI());
        dbSet('user:' + userId + ':lastName', 'Vance', this.MULTI());

    },function() {
        okWeAreDone()
    }
);

9
Wir haben einen kurzen Blick auf flow-js, step und async geworfen und es scheint, dass sie sich nur mit der Reihenfolge der Funktionsausführung befassen. In meinem Fall gibt es in jeder Einrückung Zugriff auf Inline-Schließvariablen. So funktionieren die Funktionen beispielsweise wie folgt: HTTP-Anforderung / res abrufen, Benutzer-ID von DB für Cookie abrufen, E-Mail für die spätere Benutzer-ID abrufen, weitere Daten für die spätere E-Mail abrufen, ..., X für später abrufen Y, ... Wenn ich mich nicht irre, stellen diese Frameworks nur sicher, dass asynchrone Funktionen in der richtigen Reihenfolge ausgeführt werden, aber in jedem Funktionskörper gibt es keine Möglichkeit, die von den Closures (?) Natürlich bereitgestellte Variable zu erhalten. Danke :)
Kay Pale

9
In Bezug auf die Rangfolge dieser Bibliotheken habe ich bei Github die Anzahl der "Sterne" für jede Bibliothek überprüft. Async hat mit ca. 3000 am meisten, Step mit ca. 1000 am nächsten, die anderen sind deutlich weniger. Natürlich machen sie nicht alle dasselbe :-)
kgilpin

3
@KayPale Ich verwende normalerweise async.waterfall und habe manchmal meine eigenen Funktionen für jede Stufe / jeden Schritt, die die Anforderungen des nächsten Schritts weitergeben, oder definiere Variablen vor dem Aufruf von async.METHOD, damit sie in der Downline verfügbar sind. Verwendet auch METHODNAME.bind (...) für meine asynchronen. * -Aufrufe, was auch ziemlich gut funktioniert.
Tracker1

Eine kurze Frage: Sind in Ihrer Liste der Module die letzten beiden gleich? Dh "async.js" und "async"
dari0h

18

Zum größten Teil würde ich Daniel Vassallo zustimmen. Wenn Sie eine komplizierte und tief verschachtelte Funktion in separate benannte Funktionen aufteilen können, ist dies normalerweise eine gute Idee. In Zeiten, in denen es sinnvoll ist, dies innerhalb einer einzelnen Funktion zu tun, können Sie eine der vielen verfügbaren asynchronen Bibliotheken von node.js verwenden. Die Leute haben sich viele verschiedene Möglichkeiten ausgedacht, um dies anzugehen. Schauen Sie sich also die Modulseite von node.js an und sehen Sie, was Sie denken.

Ich habe selbst ein Modul dafür geschrieben, async.js . Mit diesem Beispiel könnte das obige Beispiel aktualisiert werden auf:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  async.series({
    someData: async.apply(getSomeDate, client),
    someOtherData: async.apply(getSomeOtherDate, client),
    moreData: async.apply(getMoreData, client)
  },
  function (err, results) {
    var html = "<h1>Demo page</h1>";
    html += "<p>" + results.someData + "</p>";
    html += "<p>" + results.someOtherData + "</p>";
    html += "<p>" + results.moreData + "</p>";
    res.write(html);
    res.end();
  });
});

Eine schöne Sache bei diesem Ansatz ist, dass Sie Ihren Code schnell ändern können, um die Daten parallel abzurufen, indem Sie die Funktion 'series' in 'parallel' ändern. Darüber hinaus funktioniert async.js auch im Browser, sodass Sie dieselben Methoden wie in node.js verwenden können, wenn Sie auf kniffligen asynchronen Code stoßen.

Hoffe das ist nützlich!


Hallo Caolan und danke für die Antwort! In meinem Fall gibt es in jeder Einrückung Zugriff auf Inline-Schließvariablen. So funktionieren die Funktionen beispielsweise wie folgt: HTTP-Anforderung / res abrufen, Benutzer-ID von DB für Cookie abrufen, E-Mail für die spätere Benutzer-ID abrufen, weitere Daten für die spätere E-Mail abrufen, ..., X für später abrufen Y, ... Wenn ich mich nicht irre, stellt der von Ihnen vorgeschlagene Code nur sicher, dass asynchrone Funktionen in der richtigen Reihenfolge ausgeführt werden. In jedem Funktionskörper gibt es jedoch keine Möglichkeit, die Variable zu erhalten, die auf natürliche Weise durch die Abschlüsse in meinem ursprünglichen Code bereitgestellt wird. Ist das der Fall?
Kay Pale

3
Was Sie erreichen möchten, wird architektonisch als Datenpipeline bezeichnet. In solchen Fällen können Sie den asynchronen Wasserfall verwenden.
Rudolf Meijering

18

Sie können diesen Trick mit einem Array anstelle von verschachtelten Funktionen oder einem Modul verwenden.

Weitaus augenschonender.

var fs = require("fs");
var chain = [
    function() { 
        console.log("step1");
        fs.stat("f1.js",chain.shift());
    },
    function(err, stats) {
        console.log("step2");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step3");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step4");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step5");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("done");
    },
];
chain.shift()();

Sie können die Redewendung für parallele Prozesse oder sogar parallele Prozessketten erweitern:

var fs = require("fs");
var fork1 = 2, fork2 = 2, chain = [
    function() { 
        console.log("step1");
        fs.stat("f1.js",chain.shift());
    },
    function(err, stats) {
        console.log("step2");
        var next = chain.shift();
        fs.stat("f2a.js",next);
        fs.stat("f2b.js",next);
    },
    function(err, stats) {
        if ( --fork1 )
            return;
        console.log("step3");
        var next = chain.shift();

        var chain1 = [
            function() { 
                console.log("step4aa");
                fs.stat("f1.js",chain1.shift());
            },
            function(err, stats) { 
                console.log("step4ab");
                fs.stat("f1ab.js",next);
            },
        ];
        chain1.shift()();

        var chain2 = [
            function() { 
                console.log("step4ba");
                fs.stat("f1.js",chain2.shift());
            },
            function(err, stats) { 
                console.log("step4bb");
                fs.stat("f1ab.js",next);
            },
        ];
        chain2.shift()();
    },
    function(err, stats) {
        if ( --fork2 )
            return;
        console.log("done");
    },
];
chain.shift()();

15

Ich mag async.js sehr für diesen Zweck.

Das Problem wird durch den Wasserfallbefehl gelöst:

Wasserfall (Aufgaben, [Rückruf])

Führt ein Array von Funktionen in Reihe aus, wobei jede ihre Ergebnisse an das nächste im Array weitergibt. Wenn jedoch eine der Funktionen einen Fehler an den Rückruf übergibt, wird die nächste Funktion nicht ausgeführt und der Hauptrückruf wird sofort mit dem Fehler aufgerufen.

Argumente

Aufgaben - Ein Array von Funktionen, die ausgeführt werden sollen. Jeder Funktion wird ein Rückruf (err, result1, result2, ...) übergeben, den sie nach Abschluss aufrufen muss. Das erste Argument ist ein Fehler (der null sein kann) und alle weiteren Argumente werden als Argumente an die nächste Aufgabe übergeben. Rückruf (err, [Ergebnisse]) - Ein optionaler Rückruf, der ausgeführt wird, sobald alle Funktionen abgeschlossen sind. Hiermit werden die Ergebnisse des Rückrufs der letzten Aufgabe übergeben.

Beispiel

async.waterfall([
    function(callback){
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback){
        callback(null, 'three');
    },
    function(arg1, callback){
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
    // result now equals 'done'    
});

Die Variablen req, res werden im selben Bereich wie die Funktion (req, res) {} geteilt, die den gesamten Aufruf von async.waterfall umfasste.

Nicht nur das, Async ist sehr sauber. Was ich damit meine ist, dass ich viele Fälle wie diesen ändere:

function(o,cb){
    function2(o,function(err, resp){
        cb(err,resp);
    })
}

Zum ersten:

function(o,cb){
    function2(o,cb);
}

Dann dazu:

function2(o,cb);

Dann dazu:

async.waterfall([function2,function3,function4],optionalcb)

Außerdem können viele für Async vorbereitete vorgefertigte Funktionen sehr schnell von util.js aufgerufen werden. Verketten Sie einfach, was Sie tun möchten, und stellen Sie sicher, dass o, cb universell gehandhabt wird. Dies beschleunigt den gesamten Codierungsprozess erheblich.


11

Was Sie brauchen, ist ein bisschen syntaktischer Zucker. Chek this out:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = ["<h1>Demo page</h1>"];
  var pushHTML = html.push.bind(html);

  Queue.push( getSomeData.partial(client, pushHTML) );
  Queue.push( getSomeOtherData.partial(client, pushHTML) );
  Queue.push( getMoreData.partial(client, pushHTML) );
  Queue.push( function() {
    res.write(html.join(''));
    res.end();
  });
  Queue.execute();
}); 

Ziemlich ordentlich , nicht wahr? Möglicherweise stellen Sie fest, dass HTML zu einem Array wurde. Dies liegt zum Teil daran, dass Zeichenfolgen unveränderlich sind. Daher ist es besser, wenn Sie Ihre Ausgabe in einem Array puffern, als immer größere Zeichenfolgen zu verwerfen. Der andere Grund ist wegen einer anderen schönen Syntax mit bind.

Queueim Beispiel ist wirklich nur ein Beispiel und kann zusammen mit partialwie folgt implementiert werden

// Functional programming for the rescue
Function.prototype.partial = function() {
  var fun = this,
      preArgs = Array.prototype.slice.call(arguments);
  return function() {
    fun.apply(null, preArgs.concat.apply(preArgs, arguments));
  };
};

Queue = [];
Queue.execute = function () {
  if (Queue.length) {
    Queue.shift()(Queue.execute);
  }
};

1
Queue.execute () führt die Partials einfach nacheinander aus, ohne auf die Ergebnisse von asynchronen Aufrufen zu warten.
ngn

Genau richtig, danke. Ich habe die Antwort aktualisiert. Hier ist ein Test: jsbin.com/ebobo5/edit (mit einer optionalen lastFunktion)
gblazex

Hallo Galambalazs und danke für die Antwort! In meinem Fall gibt es in jeder Einrückung Zugriff auf Inline-Schließvariablen. So funktionieren die Funktionen beispielsweise wie folgt: HTTP-Anforderung / res abrufen, Benutzer-ID von DB für Cookie abrufen, E-Mail für die spätere Benutzer-ID abrufen, weitere Daten für die spätere E-Mail abrufen, ..., X für später abrufen Y, ... Wenn ich mich nicht irre, stellt der von Ihnen vorgeschlagene Code nur sicher, dass asynchrone Funktionen in der richtigen Reihenfolge ausgeführt werden. In jedem Funktionskörper gibt es jedoch keine Möglichkeit, die Variable zu erhalten, die auf natürliche Weise durch die Abschlüsse in meinem ursprünglichen Code bereitgestellt wird. Ist das der Fall?
Kay Pale

1
Nun, Sie verlieren definitiv Verschlüsse in allen Antworten. Sie können ein Objekt im globalen Bereich für gemeinsam genutzte Daten erstellen. Also zB Ihre erste Funktion fügt hinzu obj.emailund Ihre nächste Funktion verwendet sie obj.emaildann löscht sie (oder weist sie nur zu null).
Gblazex

7

Bin verliebt Async.js , seit ich es gefunden habe. Es hat eine async.seriesFunktion, mit der Sie lange Verschachtelungen vermeiden können.

Dokumentation:-


Serie (Aufgaben, [Rückruf])

Führen Sie eine Reihe von Funktionen in Reihe aus, die jeweils ausgeführt werden, sobald die vorherige Funktion abgeschlossen wurde. [...]

Argumente

tasks- Eine Reihe von Funktionen, die ausgeführt werden sollen. Jeder Funktion wird ein Rückruf übergeben, den sie nach Abschluss aufrufen muss. callback(err, [results])- Ein optionaler Rückruf, der ausgeführt wird, sobald alle Funktionen abgeschlossen sind. Diese Funktion ruft ein Array aller Argumente ab, die an die im Array verwendeten Rückrufe übergeben wurden.


So können wir es auf Ihren Beispielcode anwenden: -

http.createServer(function (req, res) {

    res.writeHead(200, {'Content-Type': 'text/html'});

    var html = "<h1>Demo page</h1>";

    async.series([
        function (callback) {
            getSomeData(client, function (someData) { 
                html += "<p>"+ someData +"</p>";

                callback();
            });
        },

        function (callback) {
            getSomeOtherData(client, function (someOtherData) { 
                html += "<p>"+ someOtherData +"</p>";

                callback(); 
            });
        },

        funciton (callback) {
            getMoreData(client, function (moreData) {
                html += "<p>"+ moreData +"</p>";

                callback();
            });
        }
    ], function () {
        res.write(html);
        res.end();
    });
});

6

Der einfachste syntaktische Zucker, den ich gesehen habe, ist Node-Promise.

npm install node-versprechen || Git-Klon https://github.com/kriszyp/node-promise

Mit dieser Option können Sie asynchrone Methoden wie folgt verketten:

firstMethod().then(secondMethod).then(thirdMethod);

Der Rückgabewert von jedem ist als Argument im nächsten verfügbar.


3

Was Sie dort getan haben, ist, ein Asynch-Muster zu nehmen und es auf 3 nacheinander aufgerufene Funktionen anzuwenden, von denen jede darauf wartet, dass die vorherige ausgeführt wird, bevor Sie beginnen - dh Sie haben sie synchronisiert . Der Punkt bei der Asynch-Programmierung ist, dass mehrere Funktionen gleichzeitig ausgeführt werden können und nicht auf den Abschluss warten müssen.

Wenn getSomeDate () getSomeOtherDate () nichts bietet, was getMoreData () nichts bietet, warum rufen Sie sie dann nicht asynchron auf, wie es js erlaubt, oder wenn sie voneinander abhängig (und nicht asynchron) sind, schreiben Sie sie als Einzelfunktion?

Sie müssen keine Verschachtelung verwenden, um den Fluss zu steuern. Bringen Sie beispielsweise jede Funktion zum Abschluss, indem Sie eine gemeinsame Funktion aufrufen, die feststellt, wann alle drei abgeschlossen sind, und dann die Antwort sendet.


2

Angenommen, Sie könnten dies tun:

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    var html = "<h1>Demo page</h1>";
    chain([
        function (next) {
            getSomeDate(client, next);
        },
        function (next, someData) {
            html += "<p>"+ someData +"</p>";
            getSomeOtherDate(client, next);
        },
        function (next, someOtherData) {
            html += "<p>"+ someOtherData +"</p>";
            getMoreData(client, next);
        },
        function (next, moreData) {
            html += "<p>"+ moreData +"</p>";
            res.write(html);
            res.end();
        }
    ]);
});

Sie müssen chain () nur so implementieren, dass jede Funktion teilweise auf die nächste angewendet wird und sofort nur die erste Funktion aufgerufen wird:

function chain(fs) {
    var f = function () {};
    for (var i = fs.length - 1; i >= 0; i--) {
        f = fs[i].partial(f);
    }
    f();
}

Hallo ngn und danke für die Antwort! In meinem Fall gibt es in jeder Einrückung Zugriff auf Inline-Schließvariablen. So funktionieren die Funktionen beispielsweise wie folgt: HTTP-Anforderung / res abrufen, Benutzer-ID von DB für Cookie abrufen, E-Mail für die spätere Benutzer-ID abrufen, weitere Daten für die spätere E-Mail abrufen, ..., X für später abrufen Y, ... Wenn ich mich nicht irre, stellt der von Ihnen vorgeschlagene Code nur sicher, dass asynchrone Funktionen in der richtigen Reihenfolge ausgeführt werden. In jedem Funktionskörper gibt es jedoch keine Möglichkeit, die Variable zu erhalten, die auf natürliche Weise durch die Abschlüsse in meinem ursprünglichen Code bereitgestellt wird. Ist das der Fall?
Kay Pale

2

Rückruf Hölle kann leicht in reinem Javascript mit Schließung vermieden werden. Bei der folgenden Lösung wird davon ausgegangen, dass alle Rückrufe der Funktionssignatur (Fehler, Daten) folgen.

http.createServer(function (req, res) {
  var modeNext, onNext;

  // closure variable to keep track of next-callback-state
  modeNext = 0;

  // next-callback-handler
  onNext = function (error, data) {
    if (error) {
      modeNext = Infinity;
    } else {
      modeNext += 1;
    }
    switch (modeNext) {

    case 0:
      res.writeHead(200, {'Content-Type': 'text/html'});
      var html = "<h1>Demo page</h1>";
      getSomeDate(client, onNext);
      break;

    // handle someData
    case 1:
        html += "<p>"+ data +"</p>";
        getSomeOtherDate(client, onNext);
        break;

    // handle someOtherData
    case 2:
      html += "<p>"+ data +"</p>";
      getMoreData(client, onNext);
      break;

    // handle moreData
    case 3:
      html += "<p>"+ data +"</p>";
      res.write(html);
      res.end();
      break;

    // general catch-all error-handler
    default:
      res.statusCode = 500;
      res.end(error.message + '\n' + error.stack);
    }
  };
  onNext();
});

1

Ich habe kürzlich eine einfachere Abstraktion namens wait.for erstellt , um asynchrone Funktionen im Synchronisationsmodus (basierend auf Glasfasern) aufzurufen. Es ist in einem frühen Stadium, funktioniert aber. Es ist bei:

https://github.com/luciotato/waitfor

Mit wait.for können Sie jede asynchrone Standardfunktion von nodejs aufrufen, als wäre es eine Synchronisierungsfunktion.

Verwenden von wait.for Ihr Code könnte sein:

var http=require('http');
var wait=require('wait.for');

http.createServer(function(req, res) {
  wait.launchFiber(handleRequest,req, res); //run in a Fiber, keep node spinning
}).listen(8080);


//in a fiber
function handleRequest(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  var someData = wait.for(getSomeDate,client);
  html += "<p>"+ someData +"</p>";
  var someOtherData = wait.for(getSomeOtherDate,client);
  html += "<p>"+ someOtherData +"</p>";
  var moreData = wait.for(getMoreData,client);
  html += "<p>"+ moreData +"</p>";
  res.write(html);
  res.end();
};

... oder wenn Sie weniger ausführlich sein möchten (und auch Fehler hinzufügen möchten)

//in a fiber
function handleRequest(req, res) {
  try {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(
    "<h1>Demo page</h1>" 
    + "<p>"+ wait.for(getSomeDate,client) +"</p>"
    + "<p>"+ wait.for(getSomeOtherDate,client) +"</p>"
    + "<p>"+ wait.for(getMoreData,client) +"</p>"
    );
    res.end();
  }
  catch(err) {
   res.end('error '+e.message); 
  }

};

In allen Fällen sollten getSomeDate , getSomeOtherDate und getMoreData asynchrone Standardfunktionen sein, wobei der letzte Parameter ein Funktionsrückruf ist (err, data).

wie in:

function getMoreData(client, callback){
  db.execute('select moredata from thedata where client_id=?',[client.id],
       ,function(err,data){
          if (err) callback(err);
          callback (null,data);
        });
}

1

Um dieses Problem zu lösen, habe ich nodent ( https://npmjs.org/package/nodent ) geschrieben, das Ihre JS unsichtbar vorverarbeitet. Ihr Beispielcode würde (asynchron, wirklich - lesen Sie die Dokumente) werden.

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  someData <<= getSomeDate(client) ;

  html += "<p>"+ someData +"</p>";
  someOtherData <<= getSomeOtherDate(client) ;

  html += "<p>"+ someOtherData +"</p>";
  moreData <<= getMoreData(client) ;

  html += "<p>"+ moreData +"</p>";
  res.write(html);
  res.end();
});

Natürlich gibt es viele andere Lösungen, aber die Vorverarbeitung hat den Vorteil, dass der Laufzeitaufwand gering oder gar nicht ist, und dank der Unterstützung der Quellzuordnung ist das Debuggen auch einfach.


0

Ich hatte das gleiche Problem. Ich habe gesehen, dass die Hauptbibliotheken für Knoten asynchrone Funktionen ausführen, und sie bieten eine nicht natürliche Verkettung (Sie müssen drei oder mehr Methoden confs usw. verwenden), um Ihren Code zu erstellen.

Ich habe einige Wochen damit verbracht, eine Lösung zu entwickeln, die einfach und leicht zu lesen ist. Bitte versuchen Sie es mit EnqJS . Alle Meinungen werden geschätzt.

Anstatt:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  getSomeDate(client, function(someData) {
    html += "<p>"+ someData +"</p>";
    getSomeOtherDate(client, function(someOtherData) {
      html += "<p>"+ someOtherData +"</p>";
      getMoreData(client, function(moreData) {
        html += "<p>"+ moreData +"</p>";
        res.write(html);
        res.end();
      });
    });
  });

mit EnqJS:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";

  enq(function(){
    var self=this;
    getSomeDate(client, function(someData){
      html += "<p>"+ someData +"</p>";
      self.return();
    })
  })(function(){
    var self=this;
    getSomeOtherDate(client, function(someOtherData){ 
      html += "<p>"+ someOtherData +"</p>";
      self.return();
    })
  })(function(){
    var self=this;
    getMoreData(client, function(moreData) {
      html += "<p>"+ moreData +"</p>";
      self.return();
      res.write(html);
      res.end();
    });
  });
});

Beachten Sie, dass der Code größer zu sein scheint als zuvor. Aber es ist nicht wie zuvor verschachtelt. Um natürlicher zu wirken, werden die Ketten sofort aufgerufen:

enq(fn1)(fn2)(fn3)(fn4)(fn4)(...)

Und um zu sagen, dass es innerhalb der von uns aufgerufenen Funktion zurückgekehrt ist:

this.return(response)

0

Ich mache es auf eine ziemlich primitive, aber effektive Weise. ZB muss ich ein Modell mit seinen Eltern und Kindern bekommen und sagen wir, ich muss separate Abfragen für sie machen:

var getWithParents = function(id, next) {
  var getChildren = function(model, next) {
        /*... code ... */
        return next.pop()(model, next);
      },
      getParents = function(model, next) {
        /*... code ... */
        return next.pop()(model, next);
      }
      getModel = function(id, next) {
        /*... code ... */
        if (model) {
          // return next callbacl
          return next.pop()(model, next);
        } else {
          // return last callback
          return next.shift()(null, next);
        }
      }

  return getModel(id, [getParents, getChildren, next]);
}

0

Verwenden Sie Fibers https://github.com/laverdet/node-fibers. Dadurch sieht asynchroner Code synchron aus (ohne zu blockieren).

Ich persönlich benutze diesen kleinen Wrapper http://alexeypetrushin.github.com/synchronize Beispiel eines Codes aus meinem Projekt (jede Methode ist tatsächlich asynchron und arbeitet mit asynchroner Datei-E / A) Async-Control-Flow-Hilfsbibliotheken.

_update: (version, changesBasePath, changes, oldSite) ->
  @log 'updating...'
  @_updateIndex version, changes
  @_updateFiles version, changesBasePath, changes
  @_updateFilesIndexes version, changes
  configChanged = @_updateConfig version, changes
  @_updateModules version, changes, oldSite, configChanged
  @_saveIndex version
  @log "updated to #{version} version"

0

Task.js bietet Ihnen Folgendes :

spawn(function*() {
    try {
        var [foo, bar] = yield join(read("foo.json"),
                                    read("bar.json")).timeout(1000);
        render(foo);
        render(bar);
    } catch (e) {
        console.log("read failed: " + e);
    }
});

An Stelle von:

var foo, bar;
var tid = setTimeout(function() { failure(new Error("timed out")) }, 1000);

var xhr1 = makeXHR("foo.json",
                   function(txt) { foo = txt; success() },
                   function(err) { failure() });
var xhr2 = makeXHR("bar.json",
                   function(txt) { bar = txt; success() },
                   function(e) { failure(e) });

function success() {
    if (typeof foo === "string" && typeof bar === "string") {
        cancelTimeout(tid);
        xhr1 = xhr2 = null;
        render(foo);
        render(bar);
    }
}

function failure(e) {
    xhr1 && xhr1.abort();
    xhr1 = null;
    xhr2 && xhr2.abort();
    xhr2 = null;
    console.log("read failed: " + e);
}

0

Nachdem die anderen geantwortet hatten, gaben Sie an, dass Ihr Problem lokale Variablen waren. Es scheint eine einfache Möglichkeit zu sein, eine äußere Funktion zu schreiben, die diese lokalen Variablen enthält, dann eine Reihe benannter innerer Funktionen zu verwenden und namentlich darauf zuzugreifen. Auf diese Weise verschachteln Sie immer nur zwei tief, unabhängig davon, wie viele Funktionen Sie miteinander verketten müssen.

Hier ist der Versuch meines Neulings, das mysqlNode.js-Modul mit Verschachtelung zu verwenden:

function with_connection(sql, bindings, cb) {
    pool.getConnection(function(err, conn) {
        if (err) {
            console.log("Error in with_connection (getConnection): " + JSON.stringify(err));
            cb(true);
            return;
        }
        conn.query(sql, bindings, function(err, results) {
            if (err) {
                console.log("Error in with_connection (query): " + JSON.stringify(err));
                cb(true);
                return;
            }
            console.log("with_connection results: " + JSON.stringify(results));
            cb(false, results);
        });
    });
}

Das Folgende ist ein Umschreiben unter Verwendung benannter innerer Funktionen. Die äußere Funktion with_connectionkann auch als Halter für lokale Variablen verwendet werden. (Hier habe ich die Parameter bekam sql, bindings, cbdass die Handlung in einer ähnlichen Art und Weise, aber Sie können nur einige zusätzliche lokale Variablen in definieren with_connection.)

function with_connection(sql, bindings, cb) {

    function getConnectionCb(err, conn) {
        if (err) {
            console.log("Error in with_connection/getConnectionCb: " + JSON.stringify(err));
            cb(true);
            return;
        }
        conn.query(sql, bindings, queryCb);
    }

    function queryCb(err, results) {
        if (err) {
            console.log("Error in with_connection/queryCb: " + JSON.stringify(err));
            cb(true);
            return;
        }
        cb(false, results);
    }

    pool.getConnection(getConnectionCb);
}

Ich hatte gedacht, dass es vielleicht möglich sein würde, ein Objekt mit Instanzvariablen zu erstellen und diese Instanzvariablen als Ersatz für die lokalen Variablen zu verwenden. Aber jetzt finde ich, dass der obige Ansatz mit verschachtelten Funktionen und lokalen Variablen einfacher und verständlicher ist. Es scheint einige Zeit zu dauern, OO zu verlernen, wie es scheint :-)

Hier ist meine vorherige Version mit Objekt- und Instanzvariablen.

function DbConnection(sql, bindings, cb) {
    this.sql = sql;
    this.bindings = bindings;
    this.cb = cb;
}
DbConnection.prototype.getConnection = function(err, conn) {
    var self = this;
    if (err) {
        console.log("Error in DbConnection.getConnection: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    conn.query(this.sql, this.bindings, function(err, results) { self.query(err, results); });
}
DbConnection.prototype.query = function(err, results) {
    var self = this;
    if (err) {
        console.log("Error in DbConnection.query: " + JSON.stringify(err));
        self.cb(true);
        return;
    }
    console.log("DbConnection results: " + JSON.stringify(results));
    self.cb(false, results);
}

function with_connection(sql, bindings, cb) {
    var dbc = new DbConnection(sql, bindings, cb);
    pool.getConnection(function (err, conn) { dbc.getConnection(err, conn); });
}

Es stellt sich heraus, dass binddies zu einem gewissen Vorteil genutzt werden kann. Es ermöglicht mir, die etwas hässlichen anonymen Funktionen, die ich erstellt habe und die nicht viel bewirkt haben, loszuwerden, außer mich selbst an einen Methodenaufruf weiterzuleiten. Ich konnte die Methode nicht direkt übergeben, da sie mit dem falschen Wert von verbunden gewesen wäre this. Aber mit bindkann ich den Wert angeben, den thisich möchte.

function DbConnection(sql, bindings, cb) {
    this.sql = sql;
    this.bindings = bindings;
    this.cb = cb;
}
DbConnection.prototype.getConnection = function(err, conn) {
    var f = this.query.bind(this);
    if (err) {
        console.log("Error in DbConnection.getConnection: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    conn.query(this.sql, this.bindings, f);
}
DbConnection.prototype.query = function(err, results) {
    if (err) {
        console.log("Error in DbConnection.query: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    console.log("DbConnection results: " + JSON.stringify(results));
    this.cb(false, results);
}

// Get a connection from the pool, execute `sql` in it
// with the given `bindings`.  Invoke `cb(true)` on error,
// invoke `cb(false, results)` on success.  Here,
// `results` is an array of results from the query.
function with_connection(sql, bindings, cb) {
    var dbc = new DbConnection(sql, bindings, cb);
    var f = dbc.getConnection.bind(dbc);
    pool.getConnection(f);
}

Natürlich ist nichts davon richtig mit JS mit Node.js Codierung - ich habe nur ein paar Stunden damit verbracht. Aber vielleicht kann diese Technik mit ein wenig Polieren helfen?





0

Mit wire würde Ihr Code folgendermaßen aussehen:

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});

    var l = new Wire();

    getSomeDate(client, l.branch('someData'));
    getSomeOtherDate(client, l.branch('someOtherData'));
    getMoreData(client, l.branch('moreData'));

    l.success(function(r) {
        res.write("<h1>Demo page</h1>"+
            "<p>"+ r['someData'] +"</p>"+
            "<p>"+ r['someOtherData'] +"</p>"+
            "<p>"+ r['moreData'] +"</p>");
        res.end();
    });
});

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.