Node.js 连续迭代mongodb游标(在移动到下一个文档之前等待回调)
使用mongoskin,我可以执行如下查询,它将返回一个游标: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(),因为这是一
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
}