【问题标题】:Mongo returns undefined but data is thereMongo返回未定义但数据在那里
【发布时间】:2016-06-15 21:53:32
【问题描述】:

我有以下代码。使用 $geoNear,我找到了离给定 gps 坐标最近的中转站。有些人可能知道,$geoNear 只返回最近点的位置(loc)。为了获取最近站点的详细信息,我使用位置查询 STOPS 集合。

问题是,它随机返回 undefined。当我从 mongo shell 查询 stops 集合时,使用 Agency_key 和 loc 参数我确认数据在那里。我怀疑这个问题可能是由异步引起的,但我不知道为什么。 我试图将我的问题简化为 '在 forEach 循环中调用异步函数',但到目前为止我什么都没有。

为什么返回未定义?

 ...
    TmStop.aggregate([{
              $geoNear: {
                near: [lon, lat],
                maxDistance: radiusInDegrees,
                includeLocs: "distance.location",
                distanceField: "distance.calculated",
                query: {
                  agency_key: {
                    $in: agencyKeys
                  }
                }
              }
            }, {
              $project: {
                route_id: 1,
                route_type: 1,
                direction_id: 1,
                "distance.calculated": 1,
                "distance.location": 1
              }
            }])
            .exec(function(e, results) {
          if (e) {
            console.log('e->', e.errmsg);
            var res = "Something went wrong with the database: " + e;
            cb(e, res);
          } else if (!e) {
            if (results.length) {
              console.log('results->', results.length);
              var i = 0;
              results.forEach(function(result, index) {
                console.log(index, result);
                Stop.find({
                    agency_key: {
                      $in: agencyKeys
                    },
                    loc: result.distance.location
                  })
                  .exec(function(e, stop) {
                    if (e) {
                      throw new Error('Error getting stop due to:' + e);
                    }

                    var obj = {};
                    obj.route_id = result.route_id;
                    obj.route_type = result.route_type;
                    obj.direction_id = result.direction_id;
                    obj.distance = result.distance.calculated;
                    obj.locationUsed = result.distance.location;

                    // console.log('### ', index, ' ####');
                    // console.log('@Stop.find agencyKeys-> ', agencyKeys);
                    // console.log('@Stop.find loc->', result.distance.location);
                    // console.log(stop[0]);

                    obj.stop = stop[0];
                    objArr.push(obj);
                    i++;
                    if (i === results.length) {
                      cb(e, objArr);
                    }
                  });
              }); //end of forEach
            } else {
              cb(e, []);
            }
          }
        });

【问题讨论】:

    标签: javascript node.js mongodb mongoose


    【解决方案1】:

    当然。您有一个 .forEach() 循环调用异步函数,而无需等待任何(或更重要的是“所有”调用)完成。所以你的循环迭代需要尊重被调用的内部函数的完成调用。

    async 库是一个很好的工具,为了安全起见,我们将调用async.mapLimit(因为您正在发出一个数组),这将允许同时运行指定数量的操作,而不是直到堆栈限制(或可能超出不受控制的 .forEach()for 循环)。

    所以在聚合操作的回调中,将代码列表改为:

            // If aggregation query returned anything
            if (results.length) {
              console.log('results->', results.length);
    
              async.mapLimit(results,10,function(result,callback) {
                  Stop.findOne({ 
                      "agency_key": { "$in": agency_keys },
                      "loc": result.distance.location
                  },function(e,stop) {
                      result.stop = stop;      // remember that result is already a plain object
                      callback(e,result);
                  });
              },cb)               // cb is automatically passed e and results
            } else {
                cb(e,results);    // then the array was empty
            }
    

    那里有一些改进,最值得注意的是,当您处理从常规 .find() 操作返回的 mongoose 文档时,您可能已经注意到您不能向它们添加新属性,因为它们是 mongoosse 文档(更复杂的对象)而不是普通的对象。他们只是在序列化和辅助访问器方法中假装是其中之一。

    但是.aggregate()的结果其实只是一个“普通对象”。因此,无需为了分配新属性而将对象属性复制到新对象。为了将来参考,您不需要像列表中那样明确地对“Mongoose 文档”执行此操作,而只需在文档上调用 .toObject()。结果返回一个“普通对象”。但是这里需要的代码中,只是一个简单的赋值。

    还有.findOne() 的用法,而不是.find()。在您的代码中指出,无论如何您只是在寻找单数匹配并引用结果的第一个元素。因此,只要求一个结果。无论如何,查询本身也可能返回单个结果,因为“near”的精确位置是单一的,并且没有其他查询参数,但我真的手头没有这些信息。

    最后当然还有.map() 或更确切地说是.mapLimit(),毕竟您的意图是简单地从Stop 数据中为results 数组的每个元素添加一个新属性,然后返回该元素与附加属性。每次迭代都将使用修改后的数据发出提供的callback,然后在所有迭代完成后调用最终回调,该回调将包含返回的所有元素的发出数组。这基本上是您的cb 的内置参数,所以它只是以这种形式提供而不是包装在另一个函数中。

    【讨论】:

    • 非常感谢您的详细回答。我不知道聚合函数返回一个与猫鼬文档不同的普通对象。这是非常有价值的信息。我熟悉异步库。我想尝试在没有异步库的情况下编写这部分,但显然这种方法对我来说效果不佳。再次感谢。
    • @melis 你“可以”用 Promise 做类似的事情(基本上是Promise.all(array_of_queries)),但是“限制”部分(在大多数情况下是个好主意)需要一些“手-滚动”来实施。除非你真的准备好为这些事情编写大量自己的库层处理,否则通常只为已经处理它的东西包含另一个依赖关系。
    猜你喜欢
    • 2014-12-02
    • 1970-01-01
    • 2015-02-13
    • 2020-05-12
    • 2014-06-19
    • 1970-01-01
    • 1970-01-01
    • 2020-01-12
    • 1970-01-01
    相关资源
    最近更新 更多