使用JS Promises执行多个asynchronous查询来构build一个对象

在最近发现了JS的承诺之后 , 我一直在研究它们,以便我可以构build一个特定的function,允许我执行4个asynchronous查询,使用每个结果构build一个对象,最终我可以发送一个对象我的节点应用程序

  • 最后一个对象由3个包含每个查询的结果行的数组属性组成。

看来,我已经做了错误的处理承诺,但是,因为最终, game没有build立。 它作为一个空的对象发送。 这是一个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); }); }); } 

你的问题是你没有使用承诺。 所有的API都使用callback。

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

在这里输入图像描述

承诺也有一个方法,打开框,工作的价值,并返回值的另一个框(也打开任何额外的框)。 那么这个方法是:

在框中,它确实:

在这里输入图像描述 =>( 打开 。 => Ë )=> Ë

也就是说,它增加了一个处理程序,获得一个开放的框,并返回一个框。 其他一切只是结合的东西。 所有的.all都是等待一个承诺清单来解决的,这完全是一样的.then然后等待一个结果。 因为承诺是盒子,所以你可以将它们传递给它们,并返回非常酷的东西。

通常:

  • 每当你从承诺处理程序返回 (而不是拒绝),你填写它表示正常的stream程继续。
  • 每当你抛出一个承诺处理程序,你拒绝指示exceptionstream量。

所以基本上在节点上说:

  • 无论何时您返回空错误和响应,您都可以解决承诺。
  • 每当你返回一个错误而没有回应,你就拒绝承诺。

所以:

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

我认为这是更清晰的。 就像在同步代码中一样,错误在承诺链中传播 – 寻找同步模拟器以承诺代码是很常见的。

Q.all接受一个Q.all列表并等待它们完成,相反,你希望Q.nfcall将一个基于callback的API转换为promise,然后使用Q.all

那是:

  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或完全promisify它。 如果你不介意的话,我会用蓝鸟来处理,因为它具有更好的处理这些互操作案例的function,而且速度更快,如果你愿意,你可以切换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)]); }); }); 

现在你可以调用sendGame(req, res, sales, settings, categories); 在gameSucceed之外,它并不隐藏它所做的事情 –

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