wake-up-neo.net

So vermeiden Sie das lange Verschachteln asynchroner Funktionen in Node.js

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. Wenn ich also alle auf einer einzigen Seite (HTTP-Antwort) verwenden möchte, müsste ich sie alle verschachteln:

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 wird zum Problem .

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

153
Kay Pale

Interessante Beobachtung. Beachten Sie, dass Sie in JavaScript normalerweise anonyme Callback-Funktionen 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 die Callback-Logik an anderen Stellen wiederverwenden möchten, ist es oft viel einfacher, anonyme Inline-Funktionen zu lesen, wie in Ihrem Beispiel. Es erspart Ihnen auch, für alle Rückrufe einen Namen zu finden.

Beachten Sie außerdem, dass, wie in einem Kommentar unten in @pst angegeben, auf Schließvariablen innerhalb der inneren Funktionen zugegriffen wird, die obige Übersetzung keine einfache Übersetzung wäre. In solchen Fällen ist die Verwendung anonymer Inline-Funktionen noch bevorzugter. 

72
Daniel Vassallo

Kay, benutze einfach eines dieser Module.

Das wird sich drehen:

dbGet('userIdOf:bobvance', function(userId) {
    dbSet('user:' + userId + ':email', '[email protected]', function() {
        dbSet('user:' + userId + ':firstName', 'Bob', function() {
            dbSet('user:' + userId + ':lastName', 'Vance', function() {
                okWeAreDone();
            });
        });
    });
});

Das sehr gut finden:

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

    },function(userId) {
        dbSet('user:' + userId + ':email', '[email protected]', this.MULTI());
        dbSet('user:' + userId + ':firstName', 'Bob', this.MULTI());
        dbSet('user:' + userId + ':lastName', 'Vance', this.MULTI());

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

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

Viel leichter für die Augen.

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 Sprache 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()();
18
Guido

Zum größten Teil stimme ich Daniel Vassallo zu. 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 node.js-Async-Bibliotheken verwenden. Die Leute haben viele verschiedene Möglichkeiten gefunden, wie Sie das angehen können. Schauen Sie sich die Modul-Seite node.js an und sehen Sie, was Sie denken.

Ich habe selbst ein Modul geschrieben, das async.js heißt. Damit kann das obige Beispiel aktualisiert werden:

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' auf 'parallel' setzen. Außerdem funktioniert async.js Auch im Browser. Sie können also dieselben Methoden verwenden wie in node.js, wenn Sie auf einen komplizierten asynchronen Code stoßen.

Hoffe das ist nützlich!

18
Caolan

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

Das Problem wird durch einen Wasserfallbefehl gelöst:

wasserfall (Aufgaben, [Rückruf])

Führt ein Array von Funktionen in Reihe aus, wobei jeweils die Ergebnisse an das nächste im Array übergeben werden. 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 zum Ausführen, jeder Funktion wird ein Rückruf übergeben (err, result1, result2, ...), 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 Task übergeben. callback (err, [results]) - Ein optionaler Callback, der ausgeführt wird, wenn alle Funktionen abgeschlossen sind. Dadurch werden die Ergebnisse des Callbacks 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 innerhalb desselben Gültigkeitsbereichs gemeinsam mit function (req, res) {} verwendet, die den gesamten async.waterfall-Aufruf eingeschlossen hat.

Async ist nicht nur sehr sauber. Was ich damit meine, ist, dass ich viele Fälle wie folgt ä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)

Es erlaubt auch, dass viele vorgefertigte Funktionen, die für async vorbereitet sind, sehr schnell von util.js aufgerufen werden. Verketten Sie einfach, was Sie tun möchten, und stellen Sie sicher, dass o, cb universell behandelt wird. Dies beschleunigt den gesamten Codierungsprozess erheblich.

15
Grant Li

Was Sie brauchen, ist ein bisschen syntaktischer Zucker. Chek dies heraus:

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();
}); 

Hübschordentlich, nicht wahr? Möglicherweise stellen Sie fest, dass html zu einem Array wurde. Dies ist zum Teil darauf zurückzuführen, dass Strings unveränderlich sind. Sie sollten also die Ausgabe in einem Array puffern, anstatt immer größere Strings zu verwerfen. Der andere Grund liegt an einer anderen Nice-Syntax mit bind.

Queue im Beispiel ist wirklich nur ein Beispiel und kann zusammen mit partial wie 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);
  }
};
11
gblazex

Ich bin verliebt Async.js seit ich es gefunden habe. Es hat eine async.series - Funktion, 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 ist. [...]

Argumente

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


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();
    });
});
7
Salman Abbas

Der einfachste syntaktische Zucker, den ich je gesehen habe, ist das Knotenversprechen.

npm install node-promise || git clone https://github.com/kriszyp/node-promise

Damit können Sie async-Methoden wie folgt verketten:

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

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

6
Nikhil Ranjan

Was Sie dort getan haben, ist ein asynchrones Muster, und wenden Sie es auf 3 Funktionen an, die nacheinander aufgerufen werden. Jede wartet, bis der vorherige abgeschlossen ist, bevor Sie mit dem Start beginnen - d. H., Sie haben sie synchronous gemacht. Bei der asynchronen Programmierung geht es darum, dass Sie mehrere Funktionen gleichzeitig ausführen können und nicht warten müssen, bis alle abgeschlossen sind.

wenn getSomeDate () nichts für getSomeOtherDate () liefert, was nichts für getMoreData () liefert, warum rufen Sie sie nicht asynchron auf, wie es js zulässt, oder wenn sie voneinander abhängig sind (und nicht asynchron), schreiben Sie sie als einzelne Funktion?

Sie müssen keine Verschachtelung verwenden, um den Fluss zu steuern. Sie können beispielsweise jede Funktion beenden, indem Sie eine allgemeine Funktion aufrufen, die festlegt, wann alle 3 abgeschlossen sind, und dann die Antwort sendet.

3
Nick Tulett

rückrufhölle kann in reinem Javascript mit Verschluss leicht vermieden werden. Die folgende Lösung geht davon aus, dass alle Rückrufe der Signatur der Funktion (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();
});
2
kai zhu

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 nur chain () implementieren, um jede Funktion teilweise auf die nächste anzuwenden und sofort nur die erste Funktion aufzurufen:

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

Ich habe kürzlich eine einfachere Abstraktion mit dem Namen wait.for erstellt, um async-Funktionen im Synchronisationsmodus (basierend auf Fasern) aufzurufen. Es ist noch in einem frühen Stadium, funktioniert aber. Es ist bei:

https://github.com/luciotato/waitfor

Mit wait.for können Sie jede Standard-Nodejs-Async-Funktion aufrufen, als wäre es eine Sync-Funktion.

mit wait.for könnte Ihr Code 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 wollen (und auch Fehler einfangen)

//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 Standardmäßige async-Funktionen mit dem letzten Parameter a Funktion callback (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
Lucio M. Tato

Zur Lösung dieses Problems habe ich nodent ( https://npmjs.org/package/nodent ) geschrieben, das Ihre JS unsichtbar vorverarbeitet. Ihr Beispielcode würde (asynchron, wirklich - Dokumente lesen).

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 sie wenig oder keinen Laufzeit-Overhead hat und dank der Unterstützung von Quellkarten auch leicht zu debuggen ist.

1
MatAtBread

async.js funktioniert dafür gut. Ich bin auf diesen sehr nützlichen Artikel gestoßen, der die Notwendigkeit und Verwendung von async.js mit Beispielen erläutert: http://www.sebastianseilund.com/nodejs-async-in-practice

0
learner_19

Task.js bietet Ihnen folgendes an:

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
Janus Troelsen

C # -ähnliche Asyncawait ist eine andere Möglichkeit, dies zu tun

https://github.com/yortus/asyncawait

async(function(){

    var foo = await(bar());
    var foo2 = await(bar2());
    var foo3 = await(bar2());

}
0
Artur Stary

für Ihr Wissen betrachten Sie Jazz.js https://github.com/Javanile/Jazz.js/wiki/Script-showcase

 const jj = required ('jazz.js'); 

 // ultrakompatible stapel 
 jj.script ([
 a => ProcessTaskOneCallbackAtEnd (a), 
 b => ProcessTaskTwoCallbackAtEnd (b), 
 c => ProcessTaskThreeCallbackAtEnd (c))
 e => ProcessTaskFiveCallbackAtEnd (e), 
]); 

0
cicciodarkast

Mit wire würde Ihr Code so 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();
    });
});
0
Dan Key

Nachdem die anderen antworteten, gaben Sie an, dass Ihr Problem lokale Variablen war. Es scheint ein einfacher Weg, dies zu tun, indem Sie eine äußere Funktion schreiben, die diese lokalen Variablen enthält, dann eine Reihe von benannten inneren Funktionen verwenden und auf deren Namen zugreifen. Auf diese Weise werden Sie immer nur zwei Nester verschachteln, unabhängig davon, wie viele Funktionen Sie miteinander verketten müssen.

Hier ist der Versuch meines Neulings, das Modul mysql Node.js mit der 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 mit benannten inneren Funktionen. Die äußere Funktion with_connection kann auch als Halter für lokale Variablen verwendet werden. (Hier habe ich die Parameter sql, bindings, cb, die auf ähnliche Weise wirken, aber Sie können in with_connection nur einige zusätzliche lokale Variablen definieren.)

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 wäre, ein Objekt mit Instanzvariablen zu erstellen und diese Instanzvariablen als Ersatz für die lokalen Variablen zu verwenden. Nun finde ich jedoch, dass der oben beschriebene Ansatz mit verschachtelten Funktionen und lokalen Variablen einfacher und verständlicher ist. Es dauert einige Zeit, um OO zu verlernen, wie es scheint :-)

Also hier meine vorherige Version mit einem 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 bind zu einem gewissen Vorteil genutzt werden kann. Dadurch kann ich die etwas hässlichen anonymen Funktionen loswerden, die ich erstellt habe und die nicht viel getan haben, außer sich selbst an einen Methodenaufruf weiterzuleiten. Ich konnte die Methode nicht direkt übergeben, da sie mit dem falschen Wert von this verwickelt gewesen wäre. Mit bind kann ich jedoch den Wert von this angeben, den ich 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 das alles keine richtige JS mit Node.js-Codierung - ich habe nur ein paar Stunden damit verbracht. Aber vielleicht mit etwas Polieren kann diese Technik helfen?

0
hibbelig

Ich hatte das gleiche Problem. Ich habe gesehen, wie die großen libs zum Knoten asynchrone Funktionen ausgeführt werden, und sie stellen also eine nicht natürliche Verkettung dar (Sie müssen drei oder mehr Methoden verwenden, um das Programm zu erstellen.

Ich habe einige Wochen damit verbracht, eine Lösung zu entwickeln, um einfach zu lesen und einfacher zu lesen. Bitte geben Sie einen Versuch an 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 erscheint als zuvor. Aber es ist nicht wie zuvor verschachtelt .. Um die natürlichen Eigenschaften zu sehen, werden die Ketten sofort aufgerufen:

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

Und zu sagen, dass es innerhalb der Funktion, die wir nennen, zurückkehrte:

this.return(response)
0
Thadeu de Paula

Verwenden Sie Fasern https://github.com/laverdet/node-fibers asynchroner Code wirkt wie synchron (ohne zu blockieren).

Ich persönlich benutze diesen kleinen Wrapper http://alexeypetrushin.github.com/synchronize Beispiel für Code aus meinem Projekt (jede Methode ist eigentlich asynchron, arbeitet mit asynchroner Datei-IO) Ich habe sogar Angst, mir was vorzustellen Durcheinander wäre es mit Callback- oder 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

Wenn Sie "step" oder "seq" nicht verwenden möchten, versuchen Sie "line". Dies ist eine einfache Funktion, um den verschachtelten asynchronen Rückruf zu reduzieren.

https://github.com/kevin0571/node-line

0
Kevin

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

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
mvbl fst