MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Asynchrone Cursor-Iteration mit asynchroner Unteraufgabe

Der Cursor.hasNext() -Methode ist auch "asynchron", also müssen Sie await das auch. Gleiches gilt für Cursor.next() . Daher sollte die eigentliche "Schleife" wirklich ein while sein :

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
  }

}

Wie in den Kommentaren angemerkt, schließlich Cursor.hasNext() gibt false zurück wenn der Cursor tatsächlich leer ist, und Cursor.next() ist das Ding, das tatsächlich jeden Wert vom Cursor abruft. Sie könnten andere Strukturen machen und break die Schleife wenn hasNext() ist false , aber es eignet sich natürlicher für eine while .

Diese sind immer noch "asynchron", also müssen Sie await die Versprechungsauflösung bei jedem, und das war die Hauptsache, die Sie vermisst haben.

Wie bei Cursor.map() , dann übersehen Sie wahrscheinlich den Punkt, dass es mit einem async markiert werden kann Flag auch auf der bereitgestellten Funktion:

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

Aber Sie möchten das trotzdem irgendwo "wiederholen", es sei denn, Sie kommen mit der Verwendung von .pipe() davon zu einem anderen Ausgabeziel.

Auch das async/await Flags machen auch Cursor.forEach() "wieder praktischer" , da es ein häufiger Fehler war, einen "inneren" asynchronen Aufruf nicht einfach verarbeiten zu können, aber mit diesen Flags können Sie dies jetzt problemlos tun, obwohl Sie zugegebenermaßen müssen Verwenden Sie einen Rückruf, möchten Sie dies wahrscheinlich in ein Promise packen:

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

Natürlich gab es immer Möglichkeiten, dies entweder mit Rückrufen oder einfachen Promise-Implementierungen anzuwenden, aber es ist der "Zucker" von async/await als es tatsächlich viel sauberer aussieht.

NodeJS v10.x und MongoDB-Knotentreiber 3.1.x und höher

Und die bevorzugte Version verwendet AsyncIterator die jetzt in NodeJS v10 und höher aktiviert ist. Es ist eine viel sauberere Art zu iterieren

async function dbanalyze(){

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

Welche "in gewisser Weise" kommt auf die ursprünglich gestellte Frage zur Verwendung eines for zurück Schleife, da wir for-await-of ausführen können Syntax unterstützt hier iterable, das die richtige Schnittstelle unterstützt. Und der Cursor unterstützt diese Schnittstelle.

Wenn Sie neugierig sind, hier ist eine Auflistung, die ich vor einiger Zeit erstellt habe, um verschiedene Cursor-Iterationstechniken zu demonstrieren. Es enthält sogar einen Fall für asynchrone Iteratoren aus einer Generatorfunktion:

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

})();