使用Node.js进行同步数据库查询
我有一个Node.js / Express应用程序,用于查询path中的MySQL数据库并将结果显示给用户。 我的问题是如何运行查询和阻塞,直到两个查询完成之前,redirect用户到他们要求的网页?
在我的例子中,我有2个查询需要完成之前,我呈现的页面。 如果我在查询1的“结果”callback中嵌套查询2,则可以使查询同步运行。但是,查询数量增加时,这会变得非常复杂。
我如何去同步运行多个(在这种情况下是2)数据库查询,而不是在之前的查询的'结果'callback中嵌套后续查询?
我已经看了Node模块中的'Flow control / Async goodies',并尝试了stream程js,但我无法使它与asynchronous查询一起工作。
下面列出的是我试图从“/ home”路线执行的2个查询。 节点专家可以解释“正确”的方式来做到这一点。
app.get('/home', function (req,res) { var user_array = []; var title_array = []; // first query var sql = 'select user_name from users'; db.execute(sql) .addListener('row', function(r) { user_array.push( { user_name: r.user_name } ); }) .addListener('result', function(r) { req.session.user_array = user_array; }); // second query var sql = 'select title from code_samples'; db.execute(sql) .addListener('row', function(r) { title_array.push( { title: r.title } ); }) .addListener('result', function(r) { req.session.title_array = title_array; }); // because the queries are async no data is returned to the user res.render('home.ejs', {layout: false, locals: { user_name: user_array, title: title_array }}); });
节点的目标不是关心事情发生的顺序,这可能会使一些场景复杂化。 嵌套callback没有任何耻辱。 一旦习惯了它的外观,你会发现你更喜欢那种风格。 我做; 什么命令callback会触发是非常清楚的。 如果必须的话,你可以放弃匿名函数来减less冗长。
如果你愿意重构你的代码,你可以使用“典型的”嵌套callback方法。 如果你想避免callback,有许多asynchronous框架将尝试并帮助你做到这一点。 一个你可能想要检查的是async.js(https://github.com/fjakobs/async.js)。; 每个例子:
app.get('/home', function (req,res) { var lock = 2; var result = {}; result.user_array = []; result.title_array = []; var finishRequest = function(result) { req.session.title_array = result.title_array; req.session.user_array = result.user_array; res.render('home.ejs', {layout: false, locals: { user_name: result.user_array, title: result.title_array }}); }; // first query var q1 = function(fn) { var sql = 'select user_name from users'; db.execute(sql) .addListener('row', function(r) { result.user_array.push( { user_name: r.user_name } ); }) .addListener('result', function(r) { return fn && fn(null, result); }); }; // second query var q2 = function(fn) { var sql = 'select title from code_samples'; db.execute(sql) .addListener('row', function(r) { result.title_array.push( { title: r.title } ); }) .addListener('result', function(r) { return fn && fn(null, result); }); } //Standard nested callbacks q1(function (err, result) { if (err) { return; //do something} q2(function (err, result) { if (err) { return; //do something} finishRequest(result); }); }); //Using async.js async.list([ q1, q2, ]).call().end(function(err, result) { finishRequest(result); }); });
对于一次性,我可能只是使用引用计数types的方法。 只需跟踪您想要执行的查询的数量,并在完成时提交响应。
app.get('/home', function (req,res) { var lock = 2; var user_array = []; var title_array = []; var finishRequest = function() { res.render('home.ejs', {layout: false, locals: { user_name: user_array, title: title_array }}); } // first query var sql = 'select user_name from users'; db.execute(sql) .addListener('row', function(r) { user_array.push( { user_name: r.user_name } ); }) .addListener('result', function(r) { req.session.user_array = user_array; lock -= 1; if (lock === 0) { finishRequest(); } }); // second query var sql = 'select title from code_samples'; db.execute(sql) .addListener('row', function(r) { title_array.push( { title: r.title } ); }) .addListener('result', function(r) { req.session.title_array = title_array; lock -= 1; if (lock === 0) { finishRequest(); } }); });
更好的方法是在呈现响应之前,在每个“结果”callback中调用finishRequest(),检查非空数组。 在你的情况下是否会起作用取决于你的要求。
这是处理多个callback的一个非常简单的技巧。
var after = function _after(count, f) { var c = 0, results = []; return function _callback() { switch (arguments.length) { case 0: results.push(null); break; case 1: results.push(arguments[0]); break; default: results.push(Array.prototype.slice.call(arguments)); break; } if (++c === count) { f.apply(this, results); } }; };
例
用法:
var handleDatabase = after(2, function (res1, res2) { res.render('home.ejs', { locals: { r1: res1, r2: res2 }): }) db.execute(sql1).on('result', handleDatabase); db.execute(sql2).on('result', handleDatabase);
所以基本上你需要引用计数。 这是在这些情况下的标准方法。 我实际上使用这个小的效用函数来代替stream量控制。
如果你想要一个完整的stream量控制解决scheme,我会build议futuresJS
我发现asynchronous库是最好的这样的事情。 https://github.com/caolan/async#parallel
我无法testing这个或任何东西,所以如果有一些错别字,请原谅我。 我重构了你的查询函数是可重用的。 所以,调用queryRows将返回一个匹配asynchronous模块并行callback函数格式的函数。 两个查询完成后,它将调用最后一个函数,并将这两个查询的结果作为parameter passing,您可以将其读取以传递给您的模板。
function queryRows(col, table) { return function(cb) { var rows = []; db.execute('SELECT ' + col + ' FROM ' + table) .on('row', function(r) { rows.push(r) }) .on('result', function() { cb(rows); }); } } app.get('/home', function(req, res) { async.parallel({ users: queryRow('user_name', 'users'), titles: queryRow('title', 'code_samples') }, function(result) { res.render('home.ejs', { layout: false, locals: {user_name: result.users, title: result.titles} }); }); });
这里有一些解决scheme,但在我看来,最好的解决scheme是以非常简单的方式同步代码。
你可以使用“synchonize”包。
只是
npm安装同步
然后var sync = require(synchronize);
把应该同步的逻辑用到光纤中去
sync.fiber(function() { //put your logic here }
两个mysql查询的例子:
var express = require('express'); var bodyParser = require('body-parser'); var mysql = require('mysql'); var sync = require('synchronize'); var db = mysql.createConnection({ host : 'localhost', user : 'user', password : 'password', database : 'database' }); db.connect(function(err) { if (err) { console.error('error connecting: ' + err.stack); return; } }); function saveSomething() { var post = {id: newId}; //no callback here; the result is in "query" var query = sync.await(db.query('INSERT INTO mainTable SET ?', post, sync.defer())); var newId = query.insertId; post = {foreignKey: newId}; //this query can be async, because it doesn't matter in this case db.query('INSERT INTO subTable SET ?', post, function(err, result) { if (err) throw err; }); }
当调用“saveSomething()”时,它在主表中插入一行并接收最后插入的ID。 之后,下面的代码将被执行。 没有必要嵌套承诺或类似的东西。
选项一:如果你所有的查询都相互关联,那么创build存储过程,把所有的数据逻辑放到它里面并且有一个db.execute
选项二:如果您的数据库使用一个连接,然后命令保证连续执行,你可以使用这个作为asynchronous帮助
db.execute(sql1).on('row', function(r) { req.session.user_array.push(r.user); }); db.execute(sql2) .on('row', function(r) { req.session.title_array.push(r.title); }) .on('end'), function() { // render data from req.session });