Node.js 连续迭代mongodb游标(在移动到下一个文档之前等待回调)

Node.js 连续迭代mongodb游标(在移动到下一个文档之前等待回调),node.js,mongodb,mongoskin,async.js,Node.js,Mongodb,Mongoskin,Async.js,使用mongoskin,我可以执行如下查询,它将返回一个游标: myCollection.find({}, function(err, resultCursor) { resultCursor.each(function(err, result) { } } 但是,我希望为每个文档调用一些异步函数,并且只在调用后再转到游标上的下一项(类似于async.js模块中的eachSeries结构)。例如: 我怎么能这样做 谢谢 更新: 我不想使用toArray(),因为这是一

使用mongoskin,我可以执行如下查询,它将返回一个游标:

myCollection.find({}, function(err, resultCursor) {
      resultCursor.each(function(err, result) {

      }
}
但是,我希望为每个文档调用一些异步函数,并且只在调用后再转到游标上的下一项(类似于async.js模块中的eachSeries结构)。例如:

我怎么能这样做

谢谢

更新:


我不想使用
toArray()
,因为这是一个大批量操作,结果可能无法一次性放入内存。

您可以在
数组中获得结果,并使用递归函数进行迭代,类似这样的操作

myCollection.find({}).toArray(function (err, items) {
    var count = items.length;
    var fn = function () {
        externalAsyncFuntion(items[count], function () {
            count -= 1;
            if (count) fn();
        })
    }

    fn();
});
编辑:


这只适用于较小的数据集,对于较大的数据集,您应该使用其他答案中提到的游标。

如果您不想使用toArray将所有结果加载到内存中,您可以使用游标进行如下迭代

myCollection.find({}, function(err, resultCursor) {
  function processItem(err, item) {
    if(item === null) {
      return; // All done!
    }

    externalAsyncFunction(item, function(err) {
      resultCursor.nextObject(processItem);
    });

  }

  resultCursor.nextObject(processItem);
}  

您可以使用异步库执行类似的操作。这里的关键是检查当前单据是否为空。如果是,那就意味着你完了

async.series([
        function (cb) {
            cursor.each(function (err, doc) {
                if (err) {
                    cb(err);
                } else if (doc === null) {
                    cb();
                } else {
                    console.log(doc);
                    array.push(doc);
                }
            });
        }
    ], function (err) {
        callback(err, array);
    });

您可以使用简单的setTimeOut。这是在nodejs上运行的typescript中的一个示例(我通过'when'模块使用承诺,但也可以不使用承诺):


你可以利用未来:

myCollection.find({}, function(err, resultCursor) {
    resultCursor.count(Meteor.bindEnvironment(function(err,count){
        for(var i=0;i<count;i++)
        {
            var itemFuture=new Future();

            resultCursor.nextObject(function(err,item)){
                itemFuture.result(item);
            }

            var item=itemFuture.wait();
            //do what you want with the item, 
            //and continue with the loop if so

        }
    }));
});
myCollection.find({},函数(err,resultCursor){
resultCursor.count(Meteor.bindEnvironment)(函数(err,count){

对于(var i=0;i如果有人正在寻找一种实现这一点的承诺方式(与使用nextObject的回调相反),这里就是。我使用的是节点v4.2.2和mongo驱动程序v2.1.7。这是一种
Cursor.forEach()的asyncSeries版本:

要使用它,请传递游标和一个对每个文档进行异步操作的迭代器(就像对cursor.forEach所做的那样)。迭代器需要返回一个承诺,就像大多数mongodb本机驱动程序函数一样

比方说,您想更新集合
测试中的所有文档。这就是您要做的:

var theDb;
MongoClient.connect(dbUrl).then(function(db) {
  theDb = db;     // save it, we'll need to close the connection when done.
  var cur = db.collection('test').find();

  return forEachSeries(cur, function(doc) {    // this is the iterator
    return db.collection('test').updateOne(
      {_id: doc._id},
      {$set: {updated: true}}       // or whatever else you need to change
    );
    // updateOne returns a promise, if not supplied a callback. Just return it.
  });
})
.then(function(count) {
  console.log("All Done. Processed", count, "records");
  theDb.close();
})

这通过使用setImmediate处理大型数据集:

var cursor = collection.find({filter...}).cursor();

cursor.nextObject(function fn(err, item) {
    if (err || !item) return;

    setImmediate(fnAction, item, arg1, arg2, function() {
        cursor.nextObject(fn);
    });
});

function fnAction(item, arg1, arg2, callback) {
    // Here you can do whatever you want to do with your item.
    return callback();
}

使用
异步
/
等待
的更现代方法:

const cursor = db.collection("foo").find({});
while(await cursor.hasNext()) {
  const doc = await cursor.next();
  // process doc here
}
注意事项:

  • 到达时,这可能更简单
  • 您可能需要添加try/catch以进行错误检查
  • 包含的函数应该是
    async
    ,或者代码应该包装在
    (async function(){…})(
    )中,因为它使用
    wait
  • 如果需要,可以在while循环结束时添加
    wait new Promise(resolve=>setTimeout(resolve,1000));
    (暂停1秒),以显示它确实一个接一个地处理文档
您可以使用异步迭代器

constcursor=db.collection('foo').find({});
用于等待(光标的常量文档){
//做你的事
//您甚至可以在此处使用'await myAsyncOperation()'
}


Jake Archibald写了关于异步迭代器的文章,这是我在读了@user993683的答案后才知道的。

如果你在继续之前阻塞并等待异步函数完成,那么异步调用它有什么意义呢?@RotemHermon我别无选择!这不是我的函数,它是异步的。(将myAsyncFunction重命名为externalAsyncFunction…)为什么不使用
toArray()
然后使用递归函数来迭代结果?@Сааааа-好问题-我不使用toArray,因为它是一个大批量操作,完整的结果可能无法放入内存中。(我将更新问题)抱歉,我在评论中回答您的问题太慢了-我不能使用toArray,因为结果集太大。哦,好的。那么另一个答案适合您。虽然为true,但最好避免此模式,因为它会随着数据的增长而断开。此方法不适用于大数据集。我得到“RangeError:超过最大调用堆栈大小”@SoichiHayashi将异步函数或回调包装在一个
进程中。nextTick
!@SoichiHayashi跟进@zamnuts-上面的示例导致堆栈溢出的原因是,每次处理一个项时,您都会运行另一个回调来处理当前项的处理函数中的下一个项。因此t增长,循环执行更多的函数调用,每个函数调用在上一个函数调用的基础上创建一个新的堆栈框架。将异步回调包装在
进程中。nextTick
setImmediate
setTimeout
会使它在下一个循环中运行,在我们为处理每个文档而创建的调用堆栈的“外部”。光标怎么样.forEach()
?@Redsandro-cursor.forEach()不提供异步方式来发送移动到下一项的信号。您好,Antoine-这种方法的问题是,如果您需要异步地为每个记录执行某些操作,那么cursor循环将无法等待,直到完成为止。(cursor.each不提供“下一个”回调,因此只能在其中执行同步操作)。我看不到调用了
forEachSeries
的位置。调用堆栈溢出。工作得很好,谢谢。知道大型数据集是否存在任何陷阱吗?很好,这是最好的解决方案,与选择的只会崩溃的解决方案不同。您如何在节点中使用此解决方案?我在“cursor.hasNext()”处遇到以下错误:“SyntaxError:意外标识符”“@Nico,很抱歉回复太晚,但请参见注释中的第3点;)这很好,但您不需要第一行的“.cursor()”(我有一个错误)。这取决于使用的猫鼬版本。这是一个旧版本
var theDb;
MongoClient.connect(dbUrl).then(function(db) {
  theDb = db;     // save it, we'll need to close the connection when done.
  var cur = db.collection('test').find();

  return forEachSeries(cur, function(doc) {    // this is the iterator
    return db.collection('test').updateOne(
      {_id: doc._id},
      {$set: {updated: true}}       // or whatever else you need to change
    );
    // updateOne returns a promise, if not supplied a callback. Just return it.
  });
})
.then(function(count) {
  console.log("All Done. Processed", count, "records");
  theDb.close();
})
var cursor = collection.find({filter...}).cursor();

cursor.nextObject(function fn(err, item) {
    if (err || !item) return;

    setImmediate(fnAction, item, arg1, arg2, function() {
        cursor.nextObject(fn);
    });
});

function fnAction(item, arg1, arg2, callback) {
    // Here you can do whatever you want to do with your item.
    return callback();
}
const cursor = db.collection("foo").find({});
while(await cursor.hasNext()) {
  const doc = await cursor.next();
  // process doc here
}