sql >> Base de Datos >  >> NoSQL >> MongoDB

Iteración de cursor asíncrono con subtarea asíncrona

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();
  }

})();