1

我正在使用 nodejs+express+mongoose。假设我有 2 个模式和模型:“水果”和“蔬菜”。

假设我有以下内容:

var testlist = ["Tomato", "Carrot", "Orange"];
var convertedList = [];
// Assume res is the "response" object in express

我希望能够分别根据“水果”和“蔬菜”集合检查数组中的每个项目,并将它们插入到转换后的列表中,其中番茄、胡萝卜和西兰花被各自的文档替换。

下面我有一些我认为的伪代码,但不知道该怎么做。

for(var i = 0; i < testlist.length; i++) {
var fruitfind = Fruit.find({"name":testlist[i]});
var vegfind = Vegetables.find({"name":testlist[i]});

// If fruit only
if(fruitfind) {
convertedList.push(fruitfindresults);
} 
// If vegetable only
else if(vegfind) {

convertedList.push(vegfindresults);
} 
// If identified as a fruit and a vegetable (assume tomato is a doc listed under both fruit and vegetable collections)
else if (fruitfind && vegfind) {
convertedList.push(vegfindresults);
}
}

// Converted List should now contain the appropriate docs found.
res.send(convertedList) // Always appears to return empty array... how to deal with waiting for all the callbacks to finish for the fruitfind and vegfinds?

做这个的最好方式是什么?或者这甚至可能吗?

4

2 回答 2

1

假设每种水果/蔬菜只有一种,并且您打算将两种系列中都发现的蔬菜推两次。

var async = require("async"),
    testlist = ["Tomato", "Carrot", "Orange"];

async.map(testlist, function (plant, next) {
  async.parallel([function (done) {
    Fruit.findOne({"name": plant}, done);
  },
  function (done) {
    Vegetables.findOne({"name": plant}, done);
  }], function (err, plants) { // Edited: before it was (err, fruit, veggie) which is wrong
    next(err, plants);
  });
},
function (err, result) {
  var convertedList = [].concat(result);
  res.send(convertedList);
});

注意:尚未实际测试代码,但它应该可以工作。异步模块非常适合管理像这样的回调。

更新

要只获取每个水果一次,只需像这样重写 async.parallel 回调:

function (err, plants) {
  next(err, plants[0] || plants[1]);
}

.map 回调中不再需要 concat :

function (err, result) {
  res.send(result);
}
于 2013-05-06T15:14:45.770 回答
0

find是一个异步函数,它向 mongo 数据库发出请求。这意味着两件事:

  1. 这些函数不会立即返回结果。mongoose 中的find函数遵循非常常见的异步模式。它接受一个“回调”函数,它将调用结果或错误。按照节点约定,如果第一个参数不为空,则为错误。

    // So typically you'd call find like this
    SomeModel.find({your: 'conditions'}, function (err, results) { 
      if (err) {
        // some error has occurred which you must handle
      } else {
        res.send(results);
      }
    })
    // note that if code existed on the lines following the find, it would
    // be executed *before* the find completed.
    
  2. 由于每个查询都会向数据库发出另一个请求,因此您通常希望尽可能限制数量。在这种情况下,您可以使用 mongo's 一次查找所有名称,而不是按名称查找每个水果/蔬菜$in

考虑到这两件事,您的代码可能如下所示:

// here *first* we're finding fruits
Fruit.find({name: {$in: testlist}}, function (err, fruits) {

  // when the fruit request calls back with results, we find vegetables
  Vegetable.find({name: {$in: testlist}}, function (err, vegetables) {

    // finally concat and send the results
    res.send(fruits.concat(vegetables));
  });
});

要让这两个请求并行发生,需要做更多的工作。您可以使用async 之类的库,或者自己编写如下内容:

var fruits
  , vegetables
  , done = function () {
      if (fruits && vegetables) {
        res.send(fruits.concat(vegetables));
      }
    }

Fruit.find({name: {$in: testlist}}, function (err, docs) {
  fruits = docs;
  done();
});

Vegetable.find({name: {$in: testlist}}, function (err, docs) {
  vegetables = docs;
  done();
});

请注意,这里的两个示例都只是连接结果并发送它们,因为不清楚您希望如何处理结果。这意味着,例如,如果一个西红柿在两个列表中,它将出现在结果中两次,包括蔬菜和水果文档。

您还需要处理从 mongoose 返回的任何错误。

编辑:唯一命名的文档

根据您的评论,这是您可能只返回一个番茄文档(或其他水果和蔬菜的记录)的一种方式

// after retrieving fruits and vegetables, create a map which will
// serve to weed out docs with duplicate names
var map = {};

fruits.forEach(function (fruit) {
  map[fruit.name] = fruit;
});

vegetables.forEach(function (vegetable) {
  map[vegetable.name] = vegetable;
});

var results = [];

// this would sort by name
Object.keys(map).sort().forEach(function (key, i) {
  results[i] = map[key];
});

res.send(results);

请注意,如果您需要对两个查询的结果进行排序和分页或以其他方式限制,并且如果您需要,您可能宁愿考虑将文档保存在同一个集合中,这种事情会变得更加复杂。

于 2013-05-06T15:16:19.857 回答