web-dev-qa-db-fra.com

Comment éviter l'imbrication longue de fonctions asynchrones dans Node.js

Je veux créer une page qui affiche certaines données d'une base de données. J'ai donc créé des fonctions qui extraient ces données de ma base de données. Je suis juste un débutant dans Node.js, donc, autant que je sache, si je veux tous les utiliser sur une seule page (réponse HTTP), je dois les imbriquer tous:

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

S'il y a beaucoup de fonctions comme ça, alors l'imbrication devient un problème .

Y a-t-il un moyen d'éviter cela? Je suppose que cela a à voir avec la façon dont vous combinez plusieurs fonctions asynchrones, ce qui semble être quelque chose de fondamental.

153
Kay Pale

Observation intéressante. Notez qu'en JavaScript, vous pouvez normalement remplacer les fonctions de rappel anonyme inline par des variables de fonction nommées.

Le suivant:

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

Pourrait être réécrit pour ressembler à ceci:

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

Toutefois, sauf si vous envisagez de réutiliser la logique de rappel ailleurs, il est souvent beaucoup plus facile de lire des fonctions anonymes en ligne, comme dans votre exemple. Cela vous évitera également de devoir trouver un nom pour tous les rappels.

En outre, notez que, comme indiqué dans un commentaire ci-dessous, les remarques @pst , si vous accédez à des variables de fermeture dans les fonctions internes, la traduction ci-dessus ne serait pas simple. Dans de tels cas, l’utilisation de fonctions anonymes en ligne est encore préférable. 

72
Daniel Vassallo

Kay, utilisez simplement l'un de ces modules.

Cela va tourner ceci:

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

Dans ceci:

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

Vous pouvez utiliser cette astuce avec un tableau plutôt que des fonctions imbriquées ou un module.

Beaucoup plus facile sur les yeux.

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

Vous pouvez étendre l'idiome pour des processus parallèles ou même des chaînes de processus parallèles:

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

Pour la plupart, je suis d'accord avec Daniel Vassallo. Si vous pouvez diviser une fonction compliquée et profondément imbriquée en fonctions nommées distinctes, c'est généralement une bonne idée. Pour les moments où il est judicieux de le faire dans une seule fonction, vous pouvez utiliser l’une des nombreuses bibliothèques async disponibles pour node.js. Les gens ont trouvé beaucoup de façons différentes de s’y attaquer. Consultez la page des modules de node.js et voyez ce que vous en pensez.

J'ai moi-même écrit un module appelé async.js . En utilisant ceci, l'exemple ci-dessus pourrait être mis à jour pour:

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

Une bonne chose à propos de cette approche est que vous pouvez rapidement changer votre code pour récupérer les données en parallèle en modifiant la fonction 'série' en 'parallèle'. De plus, async.js fonctionnera également Fonctionnera également dans le navigateur. Vous pourrez donc utiliser les mêmes méthodes que dans node.js si vous rencontriez du code asynchrone délicat.

J'espère que c'est utile!

18
Caolan

J'aime async.js beaucoup pour cela.

Le problème est résolu par la commande cascade:

cascade (tâches, [rappel])

Exécute un tableau de fonctions en série, chacune transmettant ses résultats au suivant dans le tableau. Toutefois, si l'une des fonctions transmet une erreur au rappel, la fonction suivante n'est pas exécutée et le rappel principal est immédiatement appelé avec l'erreur.

Arguments

tâches - Un tableau de fonctions à exécuter, chaque fonction reçoit un rappel (err, result1, result2, ...) qu'elle doit appeler à la fin. Le premier argument est une erreur (qui peut être null) et tous les autres arguments seront passés comme arguments pour la tâche suivante . callback (err, [résultats]) - Rappel facultatif à exécuter une fois que toutes les fonctions sont terminées. Les résultats du rappel de la dernière tâche seront transmis.

Exemple

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

En ce qui concerne les variables req, res, elles seront partagées dans le même champ que la fonction (req, res) {}, qui englobe l'appel entier async.waterfall.

De plus, async est très propre. Ce que je veux dire, c'est que je change beaucoup de cas de ce genre:

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

Pour commencer:

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

Alors à ceci:

function2(o,cb);

Alors à ceci:

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

Cela permet également à de nombreuses fonctions prédéfinies préparées pour async d'être appelées depuis util.js très rapidement. Vous n'avez qu'à enchaîner ce que vous voulez faire, assurez-vous que cb est géré de manière universelle. Cela accélère beaucoup le processus de codage.

15
Grant Li

Ce qu'il vous faut, c'est un peu de sucre syntaxique. 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();
}); 

Prettyneat, n'est-ce pas? Vous remarquerez peut-être que le code HTML est devenu un tableau. Cela est dû en partie au fait que les chaînes sont immuables. Il est donc préférable de mettre en tampon votre sortie dans un tableau plutôt que de supprimer des chaînes de plus en plus grandes. L'autre raison est due à une autre syntaxe de Nice avec bind.

Queue dans l'exemple est en fait juste un exemple et peut être implémenté avec partial comme suit

// 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

Suis amoureux Async.js depuis que je l'ai trouvé. Il possède une fonction async.series que vous pouvez utiliser pour éviter une longue imbrication.

Documentation:-


série (tâches, [rappel])

Exécuter un tableau de fonctions en série, chacune exécutée une fois la fonction précédente terminée. [...]

Arguments

tasks - Un tableau de fonctions à exécuter, chaque fonction reçoit un rappel qu'elle doit appeler à la fin de l'opération .callback(err, [results]) - Un rappel facultatif à exécuter une fois toutes les fonctions terminées. Cette fonction obtient un tableau de tous les arguments passés aux rappels utilisés dans le tableau.


Voici comment nous pouvons l'appliquer à votre exemple de code: -

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

Le sucre syntaxique le plus simple que j'ai vu est la promesse de nœud.

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

En utilisant ceci, vous pouvez chaîner des méthodes async comme suit:

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

La valeur de retour de chacun est disponible en tant qu'argument dans la suivante.

6
Nikhil Ranjan

Ce que vous avez fait est de prendre un motif asynchrone et de l’appliquer à 3 fonctions appelées en séquence, chacune attendant que la précédente soit terminée avant de commencer - c’est-à-dire que vous les avez créées synchrone . L’intérêt de la programmation asynchrone est qu’il est possible d’exécuter plusieurs fonctions en même temps sans devoir attendre que chacune d’elles se termine.

si getSomeDate () ne fournit rien à getSomeOtherDate (), qui ne fournit rien à getMoreData (), pourquoi ne pas les appeler de manière asynchrone comme le permet js ou s'ils sont interdépendants (et non asynchrones), écrivez-les en tant que fonction unique?

Vous n'avez pas besoin d'utiliser l'imbrication pour contrôler le flux - par exemple, demandez à chaque fonction de se terminer en appelant une fonction commune qui détermine le moment où toutes les 3 sont terminées, puis envoie la réponse.

3
Nick Tulett

callback Hell peut être facilement évité en JavaScript pur avec fermeture. la solution ci-dessous suppose que tous les rappels suivent la signature de la fonction (erreur, données).

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

Supposons que vous puissiez faire ceci:

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

Il suffit d'implémenter chain () pour appliquer partiellement chaque fonction à la suivante et invoquer immédiatement uniquement la première fonction:

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

J'ai récemment créé une abstraction plus simple appelée wait.for pour appeler des fonctions asynchrones en mode synchro (basé sur les fibres). C'est à un stade précoce mais cela fonctionne. C'est a:

https://github.com/luciotato/waitfor

En utilisant wait.for, vous pouvez appeler n’importe quelle fonction async du noeud nodejs standard, comme s’il s’agissait d’une fonction de synchronisation.

en utilisant wait.for, votre code pourrait être:

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

... ou si vous voulez être moins bavard (et aussi ajouter une erreur de capture)

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

};

Dans tous les cas, getSomeDate, getSomeOtherDate et getMoreData Devraient être des fonctions asynchrones standard avec le dernier paramètre un rappel de fonction (err, data)

un péché:

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

Pour résoudre ce problème, j’ai écrit nodent ( https://npmjs.org/package/nodent ) qui traite de manière invisible votre JS. Votre exemple de code deviendrait (asynchrone, vraiment - lisez la documentation).

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

De toute évidence, il existe de nombreuses autres solutions, mais le pré-traitement présente l’avantage d’avoir peu ou pas de temps système d’exécution, et grâce au support source-map, il est également facile de déboguer.

1
MatAtBread

async.js fonctionne bien pour cela. Je suis tombé sur cet article très utile qui explique la nécessité et l'utilisation de async.js avec des exemples: http://www.sebastianseilund.com/nodejs-async-in-practice

0
learner_19

Task.js vous offre ceci:

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

Au lieu de cela:

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

Cyn-like asyncawait est une autre façon de faire

https://github.com/yortus/asyncawait

async(function(){

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

}
0
Artur Stary

pour votre information, considérez Jazz.js https://github.com/Javanile/Jazz.js/wiki/Script-showcase


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

 // pile ultra-compat 
 jj.script ([
 a => ProcessTaskOneCallbackAtEnd (a), 
 b => ProcessTaskTwoCallbackAtEnd (b), 
 c => ProcessTaskThreeCallbackAtEnd (c), 
 d => ProcessTaskFourCallbackAtEnd (d) __. e => ProcessTaskFiveCallbackAtEnd (e), 
]); 

0
cicciodarkast

Utiliser wire votre code ressemblerait à ceci:

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

Après que les autres aient répondu, vous avez déclaré que votre problème était lié aux variables locales. Pour ce faire, il semble facile d’écrire une fonction externe contenant ces variables locales, puis d’utiliser un ensemble de fonctions internes nommées et d’y accéder par leur nom. De cette façon, vous ne pourrez jamais imbriquer deux profondeurs, quel que soit le nombre de fonctions que vous devez chaîner.

Voici la tentative de mon débutant d'utiliser le module mysql Node.js avec imbrication:

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

Ce qui suit est une réécriture utilisant des fonctions internes nommées. La fonction externe with_connection peut également être utilisée en tant que détenteur pour les variables locales. (Ici, j'ai les paramètres sql, bindings, cb qui agissent de la même façon, mais vous pouvez simplement définir des variables locales supplémentaires dans 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);
}

J'avais pensé qu'il serait peut-être possible de créer un objet avec des variables d'instance et d'utiliser ces variables d'instance pour remplacer les variables locales. Mais maintenant, je trouve que l'approche ci-dessus utilisant des fonctions imbriquées et des variables locales est plus simple et plus compréhensible. Il faut un certain temps pour désapprendre OO, il semble :-)

Voici donc ma version précédente avec un objet et des variables d'instance.

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

Il s'avère que bind peut être utilisé avec un avantage certain. Cela me permet de me débarrasser des fonctions anonymes quelque peu laides que j'ai créées et qui ne font pas grand chose, sauf de se transférer elles-mêmes vers un appel de méthode. Je ne pouvais pas passer la méthode directement car elle aurait été impliquée avec la mauvaise valeur de this. Mais avec bind, je peux spécifier la valeur de this que je veux.

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

Bien sûr, rien de tout cela n’est correct avec JS avec le codage Node.js - je viens de passer quelques heures là-dessus. Mais peut-être qu'avec un peu de polissage, cette technique peut aider?

0
hibbelig

J'ai eu le même problème. J'ai vu les principales bibliothèques du noeud exécuter des fonctions asynchrones, et elles présentent un chaînage tellement naturel (vous devez utiliser trois méthodes ou plus, confs, etc.) pour générer votre code.

J'ai passé quelques semaines à développer une solution simple et facile à lire. S'il vous plaît, essayez/ EnqJS . Tous les avis seront appréciés.

Au lieu de:

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

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

Observez que le code semble être plus gros qu'avant. Mais il n’est pas imbriqué comme avant . Pour paraître plus naturel, les chaînes s’appellent immédiatement:

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

Et dire que cela est revenu, dans la fonction que nous appelons:

this.return(response)
0
Thadeu de Paula

Use Fibers - https://github.com/laverdet/node-fibers cela donne un code asynchrone à un état synchrone (sans blocage)

Personnellement, j’utilise ce petit wrapper http://alexeypetrushin.github.com/synchronize Un exemple de code de mon projet (chaque méthode est en fait asynchrone, fonctionne avec un fichier async ce serait avec des bibliothèques de rappel ou async-control-flow helper.

_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
Alexey Petrushin

Si vous ne souhaitez pas utiliser "step" ou "seq", essayez "line", une fonction simple permettant de réduire les rappels asynchrones imbriqués.

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

0
Kevin

Je le fais d'une manière assez primitive mais efficace. Par exemple. J'ai besoin d'un modèle avec ses parents et ses enfants et disons que je dois leur poser des questions distinctes:

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