node.js,express - 在循环中以同步方式一个接一个地执行mysql查询

Posted

技术标签:

【中文标题】node.js,express - 在循环中以同步方式一个接一个地执行mysql查询【英文标题】:node.js, express - executing mysql queries one after another within loops in a synchronous way 【发布时间】:2015-02-23 02:39:46 【问题描述】:

在我的 node.js,express 应用程序中,我正在使用 superagent 中间件进行 ajax 调用。该调用使用node-mysql 中间件通过相当多的数据库查询来获取复杂数组中的数据库数据。

在粘贴代码之前,我试图用文字解释我正在尝试做的事情,尽管代码足以说明它想要做什么,以及第一个里面的所有异步内容回调应该以同步的方式完成。

说明:

在第一个查询的回调中,执行了一个 for 循环多次运行第二个查询,并且在每个循环之后,只有在第二个查询的回调完成后才调用下一个循环。接下来的代码行也是一样的。

代码:

您可以但是跳过 for 循环的内部结构(以 cmets 标记),以使事情变得简单明了。

conn.query("SELECT * FROM `super_cats`",function(error, results, fields)    

        if(error)console.log("erro while fetching products for homepage "+ error);

        for(var i in results)  // FIRST FOR LOOP INSIDE THE FIRST QUERY CALLBACK

            /*Innards of for loop starts*/    
            var elem = new Object();
            var supcat_id=results[i].id;
            elem.super_id =supcat_id;
            elem.cats=new Array();
            var cat= '';
            /*Innards of for loop ends*/ 

            conn.query("SELECT * FROM `categories` WHERE `supcat_id`="+supcat_id,function(error_cats, results_cats, fields_cats)    

                if (error_cats) console.log("erro while fetching cats for menu " + error_cats);

                for(var j in results_cats) 

                    /*Innards of for loop starts*/    
                    cat= new Object();
                    var cat_id=results_cats[j].id;
                    cat.cat_id=cat_id;
                    cat.cat_name=results_cats[j].cat_name;
                    cat.subcats=new Array();    
                    /*Innards of for loop starts*/    

                    conn.query("SELECT * FROM `subcategories` WHERE `category`="+cat_id,function(error_subcats, results_subcats, fields_subcats)  
                        if (error_subcats) console.log("erro while fetching subcats for menu " + error_subcats);   
                        for(var k in results_subcats )

                            /*Innards of for loop starts*/    
                            var subcat=new Object();
                            var subcat_id=results_subcats[k].id;
                            subcat.subcat_id=subcat_id;
                            subcat.subcat_name=results_subcats[k].subcategory;
                            cat.subcats.push(subcat);
                            elem.cats.push(cat);    
                            /*Innards of for loop starts*/

                        // end of for loop for  results_subcats   

                    );

                // end of for loop for result_cats
            );   

            super_cats.push(elem);    
         // end of for supercat results   

    res.send(super_cats)    
);

我尝试使用 async 中间件但徒劳无功,因为我无法确定在这种情况下使用哪个函数。

简而言之,要求是:

1)第一个回调里面的所有异步的事情都应该用同步的方式来完成。

2)只有在所有计算完成之后才应该将响应发送到 ajax 调用,而不是在此之前(因为如果事情是异步的,因为它们在现有代码中可能会发生,不是吗?)

【问题讨论】:

我认为您应该使用async - 这将解决您的问题。 @hgoebl,我在我的问题中说我无法弄清楚我应该使用哪个异步函数。 你应该查看asyncwaterfall方法 看看 async.each 这正是你需要使用的 @VsevolodGoloviznin,我必须在 for 循环中运行数据库查询,waterfall 方法如何提供帮助? 【参考方案1】:

这可能只是语义,但重要的是要了解您不能以同步方式运行它。您必须异步运行它,并管理处理顺序以获得所需的效果。我发现更多地从我想如何转换数据(如函数式编程)而不是我将在更同步的环境中编写的命令式代码来考虑这些类型的问题是有用的。

据我所知,您希望在super_cats 中得到一个看起来像这样的数据结构:

[
  
    super_id: 1,
    cats: [
      
        cat_id: 2,
        cat_name: "Category",
        subcats: [
          
            subcat_id: 3,
            subcat_name: "Subcategory"
          ,
          ...
        ]
      ,
      ...
    ]
  ,
  ...
]

让我们首先将其提取到带有单个回调的单个函数调用中。

function getCategoryTree(callback) 

现在,让我们从顶部开始。你想运行一个异步函数(一个 SQL 查询),并且你想生成一个数组,每个结果都有一个条目。对我来说,这听起来像是map operation。但是,由于我们希望异步确定其中一个值 (cats),因此我们需要使用异步映射,即 the async library provides。

我们现在只填写async.map 签名;我们想要映射我们的results(这是我们的for循环的功能等价物),并且对于每个我们想要将结果转换为something——执行something 被称为迭代器。最后,一旦我们有了所有转换后的数组元素,我们就想调用给我们函数的回调。

function getCategoryTree(callback) 
  conn.query("SELECT * FROM `super_cats`", function(error, results, fields) 
    async.map(results, iterator, callback);
  );

让我们创建一个用于获取***类别信息的新函数,并使用它的名称代替我们的iterator 占位符。

function getCategoryTree(callback) 
  conn.query("SELECT * FROM `super_cats`", function(error, results, fields) 
    async.map(results, getSuperCategory, callback);
  );


function getSuperCategory(resultRow, callback) 

现在我们需要决定要为每个resultRow 回馈什么。根据上图,我们想要一个对象,super_id 等于行的 ID,cats 等于***类别中的所有类别。但是,由于cats 也是异步确定的,我们需要运行下一个查询并转换那些结果,然后才能继续。

与上次类似,我们希望 cats 数组中的每个项目都是一个对象,其中包含查询结果中的一些信息,但我们还想要一个 subcats 数组,它再次被异步确定,所以我们将再次使用async.map。但是,这一次,我们将使用匿名函数进行回调,因为我们想在将结果提供给更高级别的回调之前对结果进行一些处理。

function getSuperCategory(resultItem, callback) 
  var supcat_id = resultItem.id;

  conn.query("SELECT * FROM `categories` WHERE supcat_id` = " + supcat_id, function(error, results, fields) 
    async.map(results, getCategory, function(err, categories) 
      callback(err,  super_id: supcat_id, cats: categories );
    );
  );

如你所见,一旦这个async.map完成,就意味着我们拥有了这个超类下的所有类别;因此,我们可以使用我们想要在数组中的对象来调用我们的callback

现在已经完成了,我们只需要实现getCategory。它看起来与getSuperCategory 非常相似,因为我们想做基本相同的事情——对于每个结果,返回一个对象,该对象包含来自查询的一些数据,但也是一个异步组件。

function getCategory(resultItem, callback) 
  var cat_id = resultItem.id;
  var cat_name = resultItem.cat_name;

  conn.query("SELECT * FROM `subcategories` WHERE `category` = " + cat_id, function(error, results, fields) 
    async.map(results, getSubCategory, function(err, subcategories) 
      callback(err,  cat_id: cat_id, cat_name: cat_name, subcats: subcategories );
    );
  );

现在,我们只需要实现getSubCategory

function getSubCategory(resultItem, callback) 
  callback(null, 
    subcat_id: resultItem.id,
    subcat_name: resultItem.subcategory
  );

哎呀!我们需要来自getSubCategory 的数据没有异步组件!事实证明我们根本不需要最后一个async.map;我们可以使用常规的数组映射;让我们更改 getCategorygetSubCategory 以使其工作。

function getCategory(resultItem, callback) 
  var cat_id = resultItem.id;
  var cat_name = resultItem.cat_name;

  conn.query("SELECT * FROM `subcategories` WHERE `category` = " + cat_id, function(error, results, fields) 
    var subcategories = results.map(getSubCategory);
    callback(error,  cat_id: cat_id, cat_name: cat_name, subcats: subcategories );
  );


function getSubCategory(resultItem) 
  return 
    subcat_id: resultItem.id,
    subcat_name: resultItem.subcategory
  ;

值得注意的是,我们原来的方法运行良好;如果getSubCategory 有机会拥有异步组件,您可以保持原样。

就是这样!这是我在写这个答案时写的代码;请注意,我不得不稍微伪造一下 SQL,但我认为这个想法是存在的:

var async = require("async");

// fake out sql queries
queryNum = 0;
var conn = 
  query: function(query, callback) 
    queryNum++;
    var results = [1, 2, 3, 4, 5].map(function(elem) 
      return 
        id: queryNum + "-" + elem,
        cat_name: "catname-" + queryNum + "-" + elem,
        subcategory: "subcategory-" + queryNum + "-" + elem
      ;
    );
    callback(null, results, null);
  
;

function getCategoryTree(callback) 
  conn.query("SELECT * FROM `super_cats`", function(error, results, fields) 
    async.map(results, getSuperCategory, callback);
  );


function getSuperCategory(resultItem, callback) 
  var supcat_id = resultItem.id;

  conn.query("SELECT * FROM `categories` WHERE supcat_id` = " + supcat_id, function(error, results, fields) 
    async.map(results, getCategory, function(err, categories) 
      callback(err,  super_id: supcat_id, cats: categories );
    );
  );


function getCategory(resultItem, callback) 
  var cat_id = resultItem.id;
  var cat_name = resultItem.cat_name;

  conn.query("SELECT * FROM `subcategories` WHERE `category` = " + cat_id, function(error, results, fields) 
    var subcategories = results.map(getSubCategory);
    callback(error,  cat_id: cat_id, cat_name: cat_name, subcats: subcategories );
  );


function getSubCategory(resultItem) 
  return 
    subcat_id: resultItem.id,
    subcat_name: resultItem.subcategory
  ;


getCategoryTree(function(err, result) 
  console.log(JSON.stringify(result, null, "  "));
);

这里有一些效率低下的地方,但为了简单起见,我已经掩盖了它们。例如,不是一遍又一遍地运行第二个子查询,您可以一次查询所有类别 ID,然后一次查询所有类别,等等。然后,一旦您拥有所有数据,您就可以遍历每个数组同步拉出你需要的部分。

此外,在关系数据库中存储树结构还有更好的方法;特别是,请查看 Modified Preorder Tree Traversal。

【讨论】:

对我来说是完美的解决方案。这个逻辑对我有用。异步调用对于创建节点结构结果集很重要。

以上是关于node.js,express - 在循环中以同步方式一个接一个地执行mysql查询的主要内容,如果未能解决你的问题,请参考以下文章

Node.js / Sequelize.js / Express.js - 如何插入多对多关联? (同步/异步?)

node.js 如何决定一个语句是不是被异步处理?

成功登录后的 Node.js Express 无限重定向循环

Node.js 同步循环或迭代异步语句

Node.js开发框架Express4.x

使用 express 的 Node.js 中的静态路由问题