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

时间:2014-12-25 09:42:45

标签: node.js asynchronous express node-mysql

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

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

解释

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

代码:

你可以跳过 for循环的内部(在评论中标记),以便在需要时简单易用。

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调用,而不是在之前(如果事情是异步的,就像在现有代码中一样,不会吗

1 个答案:

答案 0 :(得分:16)

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

从代码中我可以看出,您希望最终得到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循环的功能等价物),并且我们希望将每个结果转换为某些东西 - 异步函数那个的东西被称为迭代器。最后,一旦我们拥有了所有变换后的数组元素,我们就想调用赋给函数的回调函数。

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。