使用JS Promises执行多个异步查询来构建对象

时间:2014-03-25 22:34:56

标签: javascript node.js promise

最近发现了 JS promises I have been studying them以便我可以构建一个允许我执行4个异步查询的特定功能,使用每个的结果构建一个我可以的对象最后发送作为对我的节点应用程序的请求的响应。

  • 最终对象由3个数组属性组成,其中包含每个查询的结果行。

但是,似乎我在处理承诺时做错了,因为最终,game没有被构建。它作为空对象发送。 Here's a JSFiddle.

我的错误是什么?


这是我到目前为止所拥有的:

function sendGame(req, res, sales, settings, categories) {

    var game = new Object();

    game.sales = sales;

    game.settings = settings;

    game.categories = categories;



    JSONgame = JSON.stringify(game);

    res.writeHead(200, {
        'Access-Control-Allow-Origin': 'http://localhost',
            'Content-Length': JSONgame.length,
            'Content-Type': 'application/json'
    });

    res.write(JSONgame);
    res.end();

    console.log('Game: ' + JSON.stringify(game, null, 4));

    console.log('--------------------------------------');

    console.log('User ' + req.body.username + ' successfully retrieved game!');
}

function retrieveSales(req, connection, timeFrame) {

    console.log('User ' + req.body.username + ' retrieving sales...');

    connection.query('select * from sales_entries where date BETWEEN ? AND ?', timeFrame,

    function (err, rows, fields) {
        if (err) {
            callback(new Error('Failed to connect'), null);
        } else {
            sales = [];
            for (x = 0; x < rows.length; x++) {
                sales.push(rows[x]);
            }
            //console.log('Sales: ' + JSON.stringify(sales, null, 4));
            return sales;
        }
    });
}
为了便于阅读,省略了

retrieveCategories()retrieveSettings();它们与retrieveSales()大致相同。

function gameSucceed(req, res) {

    console.log('User ' + req.body.username + ' retrieving game...');

    var timeFrame = [moment().days(0).format("YYYY-MM-DD HH:mm:ss"), moment().days(6).format("YYYY-MM-DD HH:mm:ss")];

    var connection = createConnection();

    connection.connect(function (err) {

        if (err) return callback(new Error('Failed to connect'), null);
        console.log('Connection with the Officeball MySQL database openned for game retrieval...');

        var sales = retrieveSales(req, connection, timeFrame);
        var settings = retrieveSettings(req, connection);
        var categories = retrieveCategories(req, connection);

        var all = q.all([sales, settings, categories]);

        all.done(function () {
            sendGame(req, res, sales, settings, categories);
        });

    });

}

1 个答案:

答案 0 :(得分:4)

你的问题是你没有使用承诺。您的所有API都使用回调。

承诺就像一个封闭的盒子:

enter image description here

承诺还有一个打开框的方法,对值进行处理并返回值上的另一个框(同时打开任何其他框)。该方法为.then

在框中,它确实:

enter image description here =&gt;(open。=&gt; e)=&gt; e

也就是说,它添加了一个获取打开框并返回一个框的处理程序。其他一切只是结合了一些东西。所有.all都会等待一个承诺列表来解决,它等待结果就像.then一样。因为承诺是盒子,你可以传递它们并返回它们非常酷。

一般而言:

  • 每当您从承诺处理程序返回(而非拒绝)时,您完成它表示正常的流程延续。
  • 每当您在承诺处理程序抛出时,您拒绝指示异常流程。

所以基本上在节点中说:

  • 每当您返回空错误和响应时,您都会解析承诺。
  • 每当您返回错误且没有回复时,您都会拒绝承诺。

所以:

function myFunc(callback){
    nodeBack(function(err,data){
         if(err!== null){
             callback(new Error(err),null);
         }
         callback(data+"some processing");
     })
 });

变为:

 function myFunc(){
     return nodeBack().then(function(data){ return data+"some processing"; });
 }

我认为更清楚。错误通过promise链传播,就像在同步代码中一样 - 找到同步模拟以承诺代码是很常见的。

Q.all会获取承诺列表并等待它们完成,而您希望Q.nfcall将基于回调的API转换为承诺API,然后使用Q.all。< / p>

那是:

    var sales = Q.nfcall(retrieveSales,req, connection, timeFrame);
    var settings = Q.nfcall(retrieveSettings,req, connection);
    var categories = Q.nfcall(retrieveCategories, req, connection);

Q.nfcallerr,data约定中获取一个nodeback并将其转换为promise API。

另外,当你做

return sales;

你并没有真正返回任何东西,因为它同步返回。您需要在错误案例中使用callback,或者完全宣传它。如果你不介意的话,我会用Bluebird做的,因为它有更好的工具来处理这些互操作情况并且速度快得多,如果你希望你可以切换promisifyAll一堆Q.nfcall来电。

// somewhere, on top of file
connection = Promise.promisifyAll(connection); 

// note I'm passing just the username - passing the request breaks separation of concerns.
var retrieveSales = Promise.method(username, connection, timeFrame) {
    console.log('User ' + username + ' retrieving sales...');
    var q = 'select * from sales_entries where date BETWEEN ? AND ?';
    return connection.queryAsync(q, timeFrame).then(function(rows, fields){
       return rows;
    });
}

请注意,突然您不需要很多样板来进行查询,如果您愿意,可以直接使用queryAsync。

现在包装它的代码变为:

var gameSucceed = Promise.method(function gameSucceed(req, res) {

    console.log('User ' + req.body.username + ' retrieving game...');
    var timeFrame = [moment()....];
    var connection = Promise.promisifyAll(createConnection());

    return conn.connectAsync().then(function () {
        console.log('Connection with the ...');
        //sending req, but should really be what they use.
        return Promise.all([retrieveSales(req,conn,timeFrame),
                     retrieveSettings(req,conn),
                     retrieveCategories(req,conn)]);
    });

});

现在你可以在gameSucceed之外调用sendGame(req, res, sales, settings, categories);,而不会隐藏它的功能 -

gameSucceed(req,res).spread(function(sales,settings,cats){
    return sendGame(req,res,sales,settings,cats);
});