使用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.nfcall
在err,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); });