En utilisant mongoskin, je peux faire une requête comme celle-ci, qui retournera un curseur:
myCollection.find({}, function(err, resultCursor) {
resultCursor.each(function(err, result) {
}
}
Cependant, je voudrais appeler certaines fonctions asynchrones pour chaque document et ne passer à l'élément suivant sur le curseur qu'après que cela a été rappelé (similaire à la structure eachSeries du module async.js). Par exemple:
myCollection.find({}, function(err, resultCursor) {
resultCursor.each(function(err, result) {
externalAsyncFunction(result, function(err) {
//externalAsyncFunction completed - now want to move to next doc
});
}
}
Comment pourrais-je faire ça?
Merci
MISE À JOUR:
Je ne veux pas utiliser toArray()
car il s'agit d'une opération par lots de grande taille, et les résultats peuvent ne pas tenir en mémoire en une seule fois.
Si vous ne souhaitez pas charger tous les résultats en mémoire à l'aide de toArray, vous pouvez itérer à l'aide du curseur avec quelque chose comme ce qui suit.
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);
}
Une approche plus moderne qui utilise async
/await
:
const cursor = db.collection("foo").find({});
while(await cursor.hasNext()) {
const doc = await cursor.next();
// process doc here
}
Remarques:
async
ou le code doit être encapsulé dans (async function() { ... })()
Car il utilise await
.await new Promise(resolve => setTimeout(resolve, 1000));
(pause pendant 1 seconde) à la fin de la boucle while pour montrer qu'il traite les documents les uns après les autres.Cela fonctionne avec un grand ensemble de données en utilisant 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();
}
Si quelqu'un cherche un moyen Promise de le faire (par opposition à l'utilisation des rappels de nextObject), le voici. J'utilise Node v4.2.2 et mongo driver v2.1.7. C'est une sorte de version asyncSeries de Cursor.forEach()
:
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);
});
}
Pour l'utiliser, passez le curseur et un itérateur qui opère sur chaque document de manière asynchrone (comme vous le feriez pour Cursor.forEach). L'itérateur doit retourner une promesse, comme le font la plupart des fonctions de pilote natif mongodb.
Supposons que vous souhaitiez mettre à jour tous les documents de la collection test
. Voici comment vous le feriez:
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();
})
Vous pouvez faire quelque chose comme ça en utilisant la bibliothèque async. Le point clé ici est de vérifier si le document actuel est nul. Si c'est le cas, cela signifie que vous avez terminé.
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);
});
Vous pouvez obtenir le résultat dans un Array
et itérer en utilisant une fonction récursive, quelque chose comme ça.
myCollection.find({}).toArray(function (err, items) {
var count = items.length;
var fn = function () {
externalAsyncFuntion(items[count], function () {
count -= 1;
if (count) fn();
})
}
fn();
});
Modifier:
Cela ne s'applique qu'aux petits ensembles de données, pour les plus grands, vous devez utiliser les curseurs comme mentionné dans d'autres réponses.
Vous pouvez utiliser un avenir:
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
}
}));
});
depuis node.js v10. vous pouvez utiliser l'itérateur asynchrone
const cursor = db.collection('foo').find({});
for await (const doc of cursor) {
// do your thing
// you can even use `await myAsyncOperation()` here
}
Jake Archibald a écrit n excellent article de blog sur les itérateurs asynchrones, que j'ai appris après avoir lu la réponse de @ user993683.