Mongoskinを使用して、カーソルを返す次のようなクエリを実行できます。
_myCollection.find({}, function(err, resultCursor) {
resultCursor.each(function(err, result) {
}
}
_
ただし、各ドキュメントに対していくつかの非同期関数を呼び出し、これがコールバックされた後にカーソルの次の項目に移動したいだけです(async.jsモジュールのeachSeries構造に似ています)。例えば:
_myCollection.find({}, function(err, resultCursor) {
resultCursor.each(function(err, result) {
externalAsyncFunction(result, function(err) {
//externalAsyncFunction completed - now want to move to next doc
});
}
}
_
どうすればこれができますか?
ありがとう
更新:
toArray()
は大規模なバッチ操作であり、結果が一度にメモリに収まらない可能性があるため、使用しません。
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
/await
を使用するより現代的なアプローチ:
_const cursor = db.collection("foo").find({});
while(await cursor.hasNext()) {
const doc = await cursor.next();
// process doc here
}
_
注:
async
であるか、await
を使用するため、コードを_(async function() { ... })()
_でラップする必要があります。await new Promise(resolve => setTimeout(resolve, 1000));
(1秒間一時停止)を追加して、ドキュメントを次々に処理することを示します。これは、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();
}
誰かが(nextObjectのコールバックを使用するのではなく)これを行うPromiseの方法を探しているなら、ここにあります。私はNode v4.2.2およびmongoドライバーv2.1.7を使用しています。これはCursor.forEach()
のasyncSeriesバージョンです:
function forEachSeries(cursor, iterator) {
return new Promise(function(resolve, reject) {
var count = 0;
function processDoc(doc) {
if (doc != null) {
count++;
return iterator(doc).then(function() {
return cursor.next().then(processDoc);
});
} else {
resolve(count);
}
}
cursor.next().then(processDoc);
});
}
これを使用するには、カーソルと各ドキュメントを非同期的に処理するイテレータを渡します(Cursor.forEachの場合と同様)。反復子は、ほとんどのmongodbネイティブドライバー関数が行うように、promiseを返す必要があります。
コレクションtest
内のすべてのドキュメントを更新するとします。これはあなたがそれをする方法です:
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();
})
非同期ライブラリを使用して、このようなことができます。ここで重要なのは、現在のドキュメントがnullかどうかを確認することです。もしそうなら、それはあなたが終わったことを意味します。
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);
});
Array
で結果を取得し、次のような再帰関数を使用して反復できます。
myCollection.find({}).toArray(function (err, items) {
var count = items.length;
var fn = function () {
externalAsyncFuntion(items[count], function () {
count -= 1;
if (count) fn();
})
}
fn();
});
編集:
これは小さなデータセットにのみ適用できます。大きなデータセットの場合は、他の回答で述べたようにカーソルを使用する必要があります。
Futureを使用できます:
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
}
}));
});
node.js v10.3以降 非同期イテレーターを使用できます
const cursor = db.collection('foo').find({});
for await (const doc of cursor) {
// do your thing
// you can even use `await myAsyncOperation()` here
}
Jake Archibaldは、非同期イテレータについて すばらしいブログ記事 を書きました。これは、@ user993683の答えを読んだ後に知りました。