MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Iteração de cursor assíncrona com subtarefa assíncrona


O Cursor.hasNext() método também é "assíncrono", então você precisa await isso também. O mesmo vale para Cursor.next() . Portanto, o uso real do "loop" deve ser um 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
  }

}

Conforme observado nos comentários, eventualmente Cursor.hasNext() retornará false quando o cursor está realmente esgotado, e o Cursor.next() é o que está realmente recuperando cada valor do cursor. Você poderia fazer outras estruturas e break o loop quando hasNext() é false , mas mais naturalmente se presta a um while .

Eles ainda são "assíncronos", então você precisa await a resolução da promessa em cada um, e esse foi o principal fato que você estava perdendo.

Quanto a Cursor.map() , então você provavelmente está perdendo o ponto de que pode ser marcado com um async sinalizar na função fornecida também:
 cursor.map( async doc => {                   // We can mark as async
    let newDoc = await someAsyncMethod(doc);  // so you can then await inside
    return newDoc;
 })

Mas você ainda quer "iterar" isso em algum lugar, a menos que você possa se safar usando .pipe() para algum outro destino de saída.

Também o async/await sinalizadores também fazem Cursor.forEach() "mais prático novamente" , pois é uma falha comum não ser capaz de simplesmente lidar com uma chamada assíncrona "interna", mas com esses sinalizadores agora você pode fazer isso com facilidade, embora seja certo que você deve use um retorno de chamada, você provavelmente deseja envolver isso em um 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();
    }
  )
);

É claro que sempre houve maneiras de aplicar isso com retornos de chamada ou implementações simples de Promise, mas é o "açúcar" de async/await do que realmente faz isso parecer muito mais limpo.

NodeJS v10.xe driver de nó MongoDB 3.1.xe superior


E a versão favorita usa AsyncIterator que agora está habilitado no NodeJS v10 e superior. É uma maneira muito mais limpa de iterar
async function dbanalyze(){

  let cursor = db.collection('randomcollection').find()
  for await ( let doc of cursor ) {
    // do something with the current document
  }    
}

Que "de certa forma" volta ao que a pergunta originalmente perguntou sobre o uso de um for loop já que podemos fazer o for-await-of sintaxe aqui para suporte iterável que suporta a interface correta. E o Cursor suporta esta interface.

Se você é curioso, aqui está uma lista que preparei há algum tempo para demonstrar várias técnicas de iteração de cursor. Ele ainda inclui um caso para iteradores assíncronos de uma função geradora:
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();
  }

})();