MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Iterazione del cursore asincrono con attività secondaria asincrona

Il Cursor.hasNext() anche il metodo è "asincrono", quindi devi await anche quello. Lo stesso vale per Cursor.next() . Pertanto l'effettivo utilizzo del "ciclo" dovrebbe essere 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
  }

}

Come notato nei commenti, eventualmente Cursor.hasNext() restituirà false quando il cursore è effettivamente esaurito e il Cursor.next() è la cosa che sta effettivamente recuperando ogni valore dal cursore. Potresti fare altre strutture e break il ciclo quando hasNext() è false , ma si presta più naturalmente a un while .

Questi sono ancora "asincroni", quindi devi await la risoluzione della promessa su ciascuno, e questo era il fatto principale che ti mancava.

Come per Cursor.map() , probabilmente ti manca il punto in cui può essere contrassegnato con un async flag anche sulla funzione fornita:

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

Ma in realtà vuoi ancora "iterarlo" da qualche parte, a meno che tu non riesca a farla franca usando .pipe() verso un'altra destinazione di output.

Anche async/await i flag fanno anche Cursor.forEach() "di nuovo più pratico" , poiché è un difetto comune non essere in grado di gestire semplicemente una chiamata asincrona "interna", ma con questi flag ora puoi farlo con facilità, anche se è vero che devi devi usa una richiamata, probabilmente vorrai racchiuderlo in una promessa:

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

Ovviamente ci sono sempre stati modi per applicarlo con callback o semplici implementazioni Promise, ma è lo "zucchero" di async/await che in realtà lo rende molto più pulito.

Driver NodeJS v10.x e MongoDB Node 3.1.x e versioni successive

E la versione preferita usa AsyncIterator che ora è abilitato in NodeJS v10 e versioni successive. È un modo molto più pulito per eseguire l'iterazione

async function dbanalyze(){

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

Quale "in un certo senso" ritorna a ciò che la domanda originariamente posta sull'utilizzo di un for ciclo poiché possiamo fare il for-await-of sintassi qui per supportare iterable che supporta l'interfaccia corretta. E il Cursor supporta questa interfaccia.

Se sei curioso, ecco un elenco che ho preparato qualche tempo fa per dimostrare varie tecniche di iterazione del cursore. Include anche un caso per gli iteratori asincroni da una funzione del generatore:

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

})();