El Cursor.hasNext()
El método también es "asincrónico", por lo que debe await
eso tambien. Lo mismo ocurre con Cursor.next()
. Por lo tanto, el uso real del "bucle" realmente debería ser un 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
}
}
Como se señaló en los comentarios, eventualmente Cursor.hasNext()
devolverá false
cuando el cursor está realmente agotado, y el Cursor.next()
es lo que en realidad está recuperando cada valor del cursor. Podrías hacer otras estructuras y break
el ciclo cuando hasNext()
es false
, pero se presta más naturalmente a un while
.
Estos siguen siendo "asincrónicos", por lo que debe await
la resolución de la promesa en cada uno, y ese era el hecho principal que te faltaba.
En cuanto a Cursor.map()
, entonces probablemente te estés perdiendo el punto de que se puede marcar con un async
bandera en la función provista también:
cursor.map( async doc => { // We can mark as async
let newDoc = await someAsyncMethod(doc); // so you can then await inside
return newDoc;
})
Pero aún desea "iterar" eso en alguna parte, a menos que pueda salirse con la suya usando .pipe()
a algún otro destino de salida.
También el async/await
las banderas también hacen Cursor.forEach()
"más práctico otra vez" , ya que es un defecto común no poder manejar simplemente una llamada asíncrona "interna", pero con estas banderas ahora puede hacerlo con facilidad, aunque es cierto que debe use una devolución de llamada, probablemente quiera envolver esto en una Promesa:
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();
}
)
);
Por supuesto, siempre ha habido formas de aplicar esto con devoluciones de llamada o implementaciones simples de Promise, pero es el "azúcar" de async/await
que en realidad hace que esto se vea mucho más limpio.
NodeJS v10.x y controlador de nodo MongoDB 3.1.x y posteriores
Y la versión favorita usa AsyncIterator
que ahora está habilitado en NodeJS v10 y superior. Es una forma mucho más limpia de iterar
async function dbanalyze(){
let cursor = db.collection('randomcollection').find()
for await ( let doc of cursor ) {
// do something with the current document
}
}
Que "en cierto modo" vuelve a la pregunta original sobre el uso de un for
bucle ya que podemos hacer el for-await-of
la sintaxis aquí es compatible con iterable que admite la interfaz correcta. Y el Cursor
admite esta interfaz.
Si tiene curiosidad, aquí hay una lista que preparé hace algún tiempo para demostrar varias técnicas de iteración del cursor. Incluso incluye un caso para iteradores asíncronos de una función generadora:
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();
}
})();