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

Come faccio a riavviare condizionalmente la catena di promesse dall'inizio?

In breve, in questo caso non è necessario farlo. Ma c'è una spiegazione più lunga.

Se la tua versione di MongoDB lo supporta, puoi semplicemente usare $sample pipeline di aggregazione dopo le condizioni della query iniziale per ottenere la selezione "casuale".

Naturalmente, in ogni caso, se qualcuno non è idoneo perché ha già "vinto", contrassegnalo semplicemente come tale, o direttamente in un'altra serie di risultati tabulati. Ma il caso generale di "esclusione" qui è semplicemente modificare la query per escludere i "vincitori" da possibili risultati.

Tuttavia, in realtà dimostrerò "interrompere un ciclo" almeno in un senso "moderno" anche se in realtà non ne hai bisogno per ciò che devi effettivamente fare qui, che in realtà è modificare invece la query da escludere.

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

E ovviamente con entrambi gli approcci i risultati sono casuali ogni volta e i precedenti "vincitori" sono esclusi dalla selezione nella query stessa. L'"interruzione del ciclo" qui viene semplicemente utilizzata per continuare a produrre risultati fino a quando non ci saranno più possibili vincitori.

Una nota sui metodi di "interruzione del ciclo"

La raccomandazione generale nei moderni ambienti node.js sarebbe quella incorporata in async/await/yield funzionalità ora incluse come attivate per impostazione predefinita nelle versioni v8.x.x. Queste versioni raggiungeranno il supporto a lungo termine (LTS) nell'ottobre di quest'anno (al momento della stesura) e seguendo la mia personale "regola dei tre mesi", quindi qualsiasi nuovo lavoro dovrebbe essere basato su cose che sarebbero attuali in quel momento.

I casi alternativi qui sono presentati tramite async.await come una dipendenza dalla libreria separata. O altrimenti come una dipendenza dalla libreria separata utilizzando "Bluebird" Promise.coroutine , con quest'ultimo caso in cui potresti alternativamente utilizzare Promise.try , ma se hai intenzione di includere una libreria per ottenere quella funzione, allora potresti anche usare l'altra funzione che implementa l'approccio sintattico più moderno.

Quindi "mentre" (gioco di parole non inteso) dimostrando "infrangere una promessa/richiamare" loop, la cosa principale che dovrebbe davvero essere tolta da qui è il diverso processo di query, che in realtà esegue l'"esclusione" che si stava tentando di implementare in un "ciclo" fino a quando non è stato selezionato il vincitore casuale.

Il caso reale è che i dati lo determinano meglio. Ma l'intero esempio mostra almeno i modi in cui "sia" la selezione che l'"interruzione del ciclo" possono essere applicate.