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

Wie kann ich die Promise-Kette bedingt von Anfang an neu starten?

Kurz gesagt, das müssen Sie in diesem Fall eigentlich nicht tun. Aber es gibt eine längere Erklärung.

Wenn Ihre MongoDB-Version dies unterstützt, können Sie einfach den $sample Aggregationspipeline nach Ihren anfänglichen Abfragebedingungen, um die "zufällige" Auswahl zu erhalten.

Natürlich in jedem Fall, wenn jemand nicht teilnahmeberechtigt ist, weil er bereits "gewonnen" hat, markieren Sie ihn einfach als solchen, entweder direkt in einer anderen tabellarischen Ergebnisliste. Aber der allgemeine Fall von "Ausschluss" besteht hier darin, einfach die Abfrage zu modifizieren, um die "Gewinner" von möglichen Ergebnissen auszuschließen.

Ich werde jedoch das „Brechen einer Schleife“ zumindest in einem „modernen“ Sinne demonstrieren, obwohl Sie dies eigentlich nicht für das benötigen, was Sie hier tatsächlich tun müssen, nämlich die Abfrage so zu ändern, dass sie stattdessen ausgeschlossen wird.

const MongoClient = require('mongodb').MongoClient,
      whilst = require('async').whilst,
      BPromise = require('bluebird');

const users = [
  'Bill',
  'Ted',
  'Fred',
  'Fleur',
  'Ginny',
  'Harry'
];

function log (data) {
  console.log(JSON.stringify(data,undefined,2))
}

const oneHour = ( 1000 * 60 * 60 );

(async function() {

  let db;

  try {
    db = await MongoClient.connect('mongodb://localhost/raffle');

    const collection = db.collection('users');

    // Clean data
    await collection.remove({});

    // Insert some data
    let inserted = await collection.insertMany(
      users.map( name =>
        Object.assign({ name },
          ( name !== 'Harry' )
            ? { updated: new Date() }
            : { updated: new Date( new Date() - (oneHour * 2) ) }
        )
      )
    );
    log(inserted);

    // Loop with aggregate $sample
    console.log("Aggregate $sample");

    while (true) {
      let winner = (await collection.aggregate([
        { "$match": {
          "updated": {
            "$gte": new Date( new Date() - oneHour ),
            "$lt": new Date()
          },
          "isWinner": { "$ne": true }
        }},
        { "$sample": { "size": 1 } }
      ]).toArray())[0];

      if ( winner !== undefined ) {
        log(winner);    // Picked winner
        await collection.update(
          { "_id": winner._id },
          { "$set": { "isWinner": true } }
        );
        continue;
      }
      break;
    }

    // Reset data state
    await collection.updateMany({},{ "$unset": { "isWinner": "" } });

    // Loop with random length
    console.log("Math random selection");
    while (true) {
      let winners = await collection.find({
        "updated": {
          "$gte": new Date( new Date() - oneHour ),
          "$lt": new Date()
        },
        "isWinner": { "$ne": true }
      }).toArray();

      if ( winners.length > 0 ) {
        let winner = winners[Math.floor(Math.random() * winners.length)];
        log(winner);
        await collection.update(
          { "_id": winner._id },
          { "$set": { "isWinner": true } }
        );
        continue;
      }
      break;
    }

    // Reset data state
    await collection.updateMany({},{ "$unset": { "isWinner": "" } });

    // Loop async.whilst
    console.log("async.whilst");

    // Wrap in a promise to await
    await new Promise((resolve,reject) => {
      var looping = true;
      whilst(
        () => looping,
        (callback) => {
          collection.find({
            "updated": {
              "$gte": new Date( new Date() - oneHour ),
              "$lt": new Date()
            },
            "isWinner": { "$ne": true }
          })
          .toArray()
          .then(winners => {
            if ( winners.length > 0 ) {
              let winner = winners[Math.floor(Math.random() * winners.length)];
              log(winner);
              return collection.update(
                { "_id": winner._id },
                { "$set": { "isWinner": true } }
              );
            } else {
              looping = false;
              return
            }
          })
          .then(() => callback())
          .catch(err => callback(err))
        },
        (err) => {
          if (err) reject(err);
          resolve();
        }
      );
    });

    // Reset data state
    await collection.updateMany({},{ "$unset": { "isWinner": "" } });

    // Or synatax for Bluebird coroutine where no async/await
    console.log("Bluebird coroutine");

    await BPromise.coroutine(function* () {
      while(true) {
        let winners = yield collection.find({
          "updated": {
            "$gte": new Date( new Date() - oneHour ),
            "$lt": new Date()
          },
          "isWinner": { "$ne": true }
        }).toArray();

        if ( winners.length > 0 ) {
          let winner = winners[Math.floor(Math.random() * winners.length)];
          log(winner);
          yield collection.update(
            { "_id": winner._id },
            { "$set": { "isWinner": true } }
          );
          continue;
        }
        break;
      }
    })();

  } catch(e) {
    console.error(e)
  } finally {
    db.close()
  }
})()

Und natürlich sind bei beiden Ansätzen die Ergebnisse jedes Mal zufällig und frühere "Gewinner" werden von der Auswahl in der eigentlichen Abfrage selbst ausgeschlossen. Der "Loop Break" dient hier lediglich dazu, Ergebnisse so lange auszugeben, bis es keine möglichen Gewinner mehr geben kann.

Ein Hinweis zu den "Loop Breaking"-Methoden

Die allgemeine Empfehlung in modernen node.js-Umgebungen wäre das eingebaute async/await/yield Funktionen, die jetzt in v8.x.x-Releases standardmäßig aktiviert sind. Diese Versionen werden im Oktober dieses Jahres (zum Zeitpunkt des Schreibens) auf den Long Term Support (LTS) kommen und meiner persönlichen „Drei-Monats-Regel“ folgen, dann sollten alle neuen Arbeiten auf Dingen basieren, die zu diesem Zeitpunkt aktuell wären.

Die alternativen Fälle hier werden über async.await dargestellt als separate Bibliotheksabhängigkeit. Oder als separate Bibliotheksabhängigkeit mit "Bluebird" Promise.coroutine , wobei im letzteren Fall alternativ Promise.try , aber wenn Sie eine Bibliothek einbinden, um diese Funktion zu erhalten, können Sie genauso gut die andere Funktion verwenden, die den moderneren Syntaxansatz implementiert.

Also "während" (Wortspiel nicht beabsichtigt) demonstriert "Versprechen/Rückruf brechen" Schleife, die Hauptsache, die hier wirklich weggenommen werden sollte, ist der andere Abfrageprozess, der tatsächlich den "Ausschluss" durchführt, der versucht wurde, in einer "Schleife" zu implementieren, bis der zufällige Gewinner ausgewählt wurde.

Der tatsächliche Fall ist die Daten bestimmt dies am besten. Aber das ganze Beispiel zeigt zumindest Wege auf, wie "sowohl" die Selektion als auch der "Loop Break" angewendet werden können.