MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Itération de curseur asynchrone avec sous-tâche asynchrone

Le Cursor.hasNext() la méthode est également "asynchrone", vous devez donc await ça aussi. Idem pour Cursor.next() . Par conséquent, l'utilisation réelle de la "boucle" devrait vraiment être 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
  }

}

Comme indiqué dans les commentaires, éventuellement Cursor.hasNext() renverra false lorsque le curseur est réellement épuisé, et le Cursor.next() est la chose qui récupère réellement chaque valeur du curseur. Vous pourriez faire d'autres structures et break la boucle lorsque hasNext() est false , mais il se prête plus naturellement à un while .

Celles-ci sont toujours "asynchrones", vous devez donc await la résolution de la promesse sur chacun, et c'était le fait principal qui vous manquait.

Comme pour Cursor.map() , alors vous manquez probablement le point qu'il peut être marqué avec un async flag sur la fonction fournie également :

 cursor.map( async doc => {                   // We can mark as async
    let newDoc = await someAsyncMethod(doc);  // so you can then await inside
    return newDoc;
 })

Mais vous voulez toujours "itérer" cela quelque part, à moins que vous ne puissiez vous en sortir en utilisant .pipe() vers une autre destination de sortie.

Aussi le async/await les drapeaux font aussi Cursor.forEach() "plus pratique à nouveau" , car c'est un défaut courant de ne pas pouvoir gérer simplement un appel asynchrone "interne", mais avec ces drapeaux, vous pouvez maintenant le faire facilement, bien qu'il soit vrai que vous devez utilisez un rappel, vous voudrez probablement l'envelopper dans une promesse :

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

Bien sûr, il y a toujours eu des moyens d'appliquer cela avec des rappels ou des implémentations simples de Promise, mais c'est le "sucre" de async/await cela rend en fait cela beaucoup plus propre.

NodeJS v10.x et pilote de nœud MongoDB 3.1.x et versions ultérieures

Et la version préférée utilise AsyncIterator qui est maintenant activé dans NodeJS v10 et versions ultérieures. C'est une façon beaucoup plus propre d'itérer

async function dbanalyze(){

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

Qui "d'une certaine manière" revient à ce que la question posée à l'origine était d'utiliser un for boucle puisque nous pouvons faire le for-await-of syntaxe ici prenant en charge iterable qui prend en charge l'interface correcte. Et le Cursor prend en charge cette interface.

Si vous êtes curieux, voici une liste que j'ai préparée il y a quelque temps pour démontrer diverses techniques d'itération du curseur. Il inclut même un cas pour les itérateurs asynchrones d'une fonction génératrice :

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

})();