MongoDB Node.js each方法

4

我有一个数据数组,将其存储在数据库中。当我查找数据是否已经存在时,即使我使用limit(1),each()也会被调用两次。我不知道这里发生了什么...

collection.find({
    month: 'april'
}).limit(1).count(function(err, result){
    console.log('counter', result);
});

collection.find({
    month: 'april'
}).limit(1).each(function(err, result){
    console.log('each', result);
});

collection.find({
    month: 'april'
}).limit(1).toArray(function(err, result){
    console.log('toArray', result);
});

目前,该收藏夹中已经存储了确切的1个四月份数据集。

以上查询将生成如下输出:

count 1
each {...}
each null
toArray {...}

在Mongo shell中,我已经检查了count()和forEach()方法,一切都按预期工作。这是驱动程序的问题吗?还是我做错了什么?

你能否在每次迭代中使用 console.log 输出 err 变量,以便查看第二次迭代时它是什么吗? - jraede
1个回答

1
这是期望的行为。驱动程序在循环中返回项目,然后在结束时返回null表示没有剩余项目。你也可以在驱动程序的示例中看到这一点:
// Find returns a Cursor, which is Enumerable. You can iterate:
collection.find().each(function(err, item) {
  if(item != null) console.dir(item);
});

如果你对详情感兴趣,你可以查看每个源代码:

if(this.items.length > 0) {
  // Trampoline all the entries
  while(fn = loop(self, callback)) fn(self, callback);
  // Call each again
  self.each(callback);
} else {
  self.nextObject(function(err, item) {

    if(err) {
      self.state = Cursor.CLOSED;
      return callback(utils.toError(err), item);
    }

>>  if(item == null) return callback(null, null);  <<
    callback(null, item);
    self.each(callback);
  })
}

在这段代码中,each 使用 loop 遍历数组中的项 (var doc = self.items.shift();)。当 this.items.length 变为 0 时,执行 else 块。else 块尝试从游标获取下一个文档。如果没有更多文档,nextObject 返回 nullitem 的值变为 null),这会导致执行 if(item == null) return callback(null, null);。可以看到回调函数使用 null 被调用,而这个 null 就是你在控制台中看到的 null
这是必要的,因为MongoDB使用游标返回匹配的文档。如果集合中有数百万个文档并且您运行find(),则不会立即返回所有文档,因为您将耗尽内存。相反,MongoDB使用游标迭代项目。 "对于大多数查询,第一批返回101个文档或足够超过1兆字节的文档。"因此,this.items.length成为第一批中存在的项目数量,但这不一定是查询结果的总文档数。这就是为什么当您遍历文档并且this.items.length变为0时,MongoDB使用游标检查是否存在更多匹配的文档。如果有,它加载下一批,否则返回null
如果您使用大的限制,就更容易理解这个问题。例如,在limit(100000)的情况下,如果MongoDB立即返回所有100,000个文档,那么您将需要大量内存。更不用说处理速度会变得多么缓慢了。相反,MongoDB会分批次返回结果。假设第一批包含101个文档,则this.items.length就变成了101,但这仅是第一批的大小,而不是结果的总数。当您迭代结果并到达当前批次中的最后一个之后的下一项(在本例中为第102项)时,MongoDB使用游标来检查是否有更多匹配的文档。如果有,就会加载下一批文档,否则加载null
但您不必在代码中烦恼于nextObject(),您只需要像MongoDB示例中那样检查null即可。

但是当this.items.length == 0时,为什么需要检查nextObject呢?这应该意味着item == null,不是吗? - Thaars
扩展我的回答。仅当结果包含大量文档时才有意义。对于limit(1)的情况,很容易判断是否还有其他项目。但是each必须保持一致,这就是为什么在最后一个项目之后它总是返回null的原因。 - Gergo Erdosi
好的,我现在明白了,谢谢。我猜,each()不是检查文档是否存在的正确函数,对吗?这就是为什么findOne()存在的原因吗?在检查null的情况下,如果在该集合中没有匹配的文档,则会返回null,并且如果文档存在并且到达集合的末尾,则也会返回null,因此我将得到错误的结果。 - Thaars
each() 用于迭代 find() 返回的项目。当您期望查询返回多个文档时,它非常有用。如果您只想检查文档是否存在,则使用 findOne() 是最简单的方法。如果该项不存在,则它将返回 null。您可以使用以下代码:collection.findOne({}, function(err, item) { if (!err && item != null) { // exists } } - Gergo Erdosi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接