我想对没有数字键(_id
)的mongoDB集合执行迭代。该集合只有随机字符串作为_id,并且集合的大小很大,因此使用.toArray()
在RAM上加载整个文档不是一个可行的选择。另外,我想对每个元素执行异步任务。由于任务的异步性质,.map()
或.each()
,.forEach()
的使用受到限制。我尝试使用那些提到的方法运行任务,但它当然与异步任务冲突,返回待处理的承诺而不是正确的结果。
示例
async function dbanalyze(){
let cursor = db.collection('randomcollection').find()
for(;;){
const el = cursor.hasNext() ? loaded.next() : null;
if(!cursor) break
await performAnalyze(cursor) // <---- this doesn't return a document but just a cursor object
}
}
如何仅使用for()
迭代mongoDB集合?
答案 0 :(得分:4)
Cursor.hasNext()
方法也是“异步”的,因此您也需要await
。同样适用于Cursor.next()
。因此,实际的“循环”用法确实应该是while
:
async function dbanalyze(){
let cursor = db.collection('randomcollection').find()
while ( await cursor.hasNext() ) { // will return false when there are no more results
let doc = await cursor.next(); // actually gets the document
// do something, possibly async with the current document
}
}
如评论中所述,当光标实际耗尽时,Cursor.hasNext()
最终将返回false
,而Cursor.next()
实际上是从光标中检索每个值。当break
为hasNext()
时,您可以执行其他结构和false
循环,但它更自然地适用于while
。
这些仍然是“异步”,因此您需要await
每个人的承诺解决方案,这是您缺少的主要事实。
至于Cursor.map()
,你可能会错过在提供的函数上用async
标记标记的点:
cursor.map( async doc => { // We can mark as async
let newDoc = await someAsyncMethod(doc); // so you can then await inside
return newDoc;
})
但你实际上仍然希望在某处“迭代”,除非你可以将.pipe()
用于其他输出目的地。
async/await
标志也使Cursor.forEach()
“更实用”,因为它的一个常见缺陷是无法简单地处理“内部”异步调用,但是使用这些标志你现在可以轻松地这样做了,但不可否认,因为你必须使用回调,你可能想把它包装在Promise中:
await new Promise((resolve, reject) =>
cursor.forEach(
async doc => { // marked as async
let newDoc = await someAsyncMethod(doc); // so you can then await inside
// do other things
},
err => {
// await was respected, so we get here when done.
if (err) reject(err);
resolve();
}
)
);
当然,总有一些方法可以应用回调或普通的Promise实现,但它是async/await
的“糖”,实际上使它看起来更清晰。
最喜欢的版本使用AsyncIterator
,现在已在NodeJS v10及更高版本中启用。这是一种更清晰的迭代方式
async function dbanalyze(){
let cursor = db.collection('randomcollection').find()
for await ( let doc of cursor ) {
// do something with the current document
}
}
哪种“在某种程度上”回到最初询问的关于使用for
循环的问题,因为我们可以在此处执行for-await-of
语法以支持iterable支持正确的界面。 Cursor
确实支持此界面。
如果你是古玩,这里是我前段时间制作的一个列表,用于展示各种光标迭代技术。它甚至包括Async Iterators from a generator function的案例:
const Async = require('async'),
{ MongoClient, Cursor } = require('mongodb');
const testLen = 3;
(async function() {
let db;
try {
let client = await MongoClient.connect('mongodb://localhost/');
let db = client.db('test');
let collection = db.collection('cursortest');
await collection.remove();
await collection.insertMany(
Array(testLen).fill(1).map((e,i) => ({ i }))
);
// Cursor.forEach
console.log('Cursor.forEach');
await new Promise((resolve,reject) => {
collection.find().forEach(
console.log,
err => {
if (err) reject(err);
resolve();
}
);
});
// Async.during awaits cursor.hasNext()
console.log('Async.during');
await new Promise((resolve,reject) => {
let cursor = collection.find();
Async.during(
(callback) => Async.nextTick(() => cursor.hasNext(callback)),
(callback) => {
cursor.next((err,doc) => {
if (err) callback(err);
console.log(doc);
callback();
})
},
(err) => {
if (err) reject(err);
resolve();
}
);
});
// async/await allows while loop
console.log('async/await while');
await (async function() {
let cursor = collection.find();
while( await cursor.hasNext() ) {
let doc = await cursor.next();
console.log(doc);
}
})();
// await event stream
console.log('Event Stream');
await new Promise((end,error) => {
let cursor = collection.find();
for ( let [k,v] of Object.entries({ end, error, data: console.log }) )
cursor.on(k,v);
});
// Promise recursion
console.log('Promise recursion');
await (async function() {
let cursor = collection.find();
function iterate(cursor) {
return cursor.hasNext().then( bool =>
(bool) ? cursor.next().then( doc => {
console.log(doc);
return iterate(cursor);
}) : Promise.resolve()
)
}
await iterate(cursor);
})();
// Uncomment if node is run with async iteration enabled
// --harmony_async_iteration
console.log('Generator Async Iterator');
await (async function() {
async function* cursorAsyncIterator() {
let cursor = collection.find();
while (await cursor.hasNext() ) {
yield cursor.next();
}
}
for await (let doc of cursorAsyncIterator()) {
console.log(doc);
}
})();
// This is supported with Node v10.x and the 3.1 Series Driver
await (async function() {
for await (let doc of collection.find()) {
console.log(doc);
}
})();
client.close();
} catch(e) {
console.error(e);
} finally {
process.exit();
}
})();