使用Node.js进行同步数据库查询

Ric*_*ick 52 database synchronous node.js

我有一个Node.js/Express应用程序,用于查询路径中的MySQL数据库并将结果显示给用户.我的问题是如何在将用户重定向到他们请求的页面之前完成两个查询之前运行查询并阻止?

在我的示例中,我有2个查询需要在呈现页面之前完成.如果我在查询1的"结果"回调中嵌套查询2,我可以使查询同步运行.但是当查询数量增加时,这将变得非常复杂.

如何同步运行多个(在本例中为2个)数据库查询而不将后续查询嵌套在先前查询的"结果"回调中?

我已经查看了Node模块中的'Flow control/Async goodies',并尝试了flow-js,但我无法使用异步查询.

下面列出了我试图从'/ 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 }});
});
Run Code Online (Sandbox Code Playgroud)

jsl*_*tts 55

节点的目标不是关心事情发生的顺序.这可能会使某些情况复杂化.嵌套回调没有羞耻感.一旦你习惯了它的外观,你可能会发现你真的更喜欢那种风格.我做; 很清楚,回调将触发什么订单.如果必须,您可以放弃匿名函数以减少冗长.

如果您愿意稍微重构代码,可以使用"典型"嵌套回调方法.如果你想避免回调,有许多异步框架会尝试帮助你做到这一点.您可能想要检查的是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);
    });

});
Run Code Online (Sandbox Code Playgroud)

对于一次性,我可能只是使用引用计数类型方法.只需跟踪要执行的查询数量,并在完成所有查询后生成响应.

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();
            }
        });
});
Run Code Online (Sandbox Code Playgroud)

更好的方法是在呈现响应之前简单地在每个'result'回调中调用finishRequest(),检查非空数组.这是否适用于您的情况取决于您的要求.


Ray*_*nos 17

这是处理多个回调的一个非常简单的技巧.

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);
    }
  };
};
Run Code Online (Sandbox Code Playgroud)

用法:

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);
Run Code Online (Sandbox Code Playgroud)

所以基本上你需要引用计数.这是这些情况下的标准方法.我实际上使用这个小实用程序功能而不是流量控制.

如果你想要一个完整的流量控制解决方案,我会推荐futureJS


log*_*yth 14

我发现async库最适合这样的事情.https://github.com/caolan/async#parallel

我无法测试这个或任何东西,所以如果有一些错别字,请原谅我.我重构了你的查询函数是可重用的.因此,调用queryRows将返回一个与async模块的并行回调函数的格式匹配的函数.两个查询完成后,它将调用最后一个函数并将两个查询的结果作为参数传递,您可以读取该参数以传递给模板.

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} 
    });
  });
});
Run Code Online (Sandbox Code Playgroud)


Esc*_*ape 6

这里有一些解决方案,但在我看来,最好的解决方案是以非常简单的方式同步编写代码。

您可以使用“同步”包。

只是

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;
    });
}
Run Code Online (Sandbox Code Playgroud)

当“saveSomething()”被调用时,它会在主表中插入一行并接收最后插入的id。之后将执行下面的代码。不需要嵌套承诺或类似的东西。