MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Como reinicio condicionalmente a cadeia de promessas desde o início?


Em resumo, você realmente não precisa fazer isso neste caso. Mas há uma explicação mais longa.

Se sua versão do MongoDB for compatível, você pode simplesmente usar o $amostra pipeline de agregação após suas condições de consulta iniciais para obter a seleção "aleatória".

É claro que, em qualquer caso, se alguém não for elegível porque já "ganhou", basta marcá-lo como tal, diretamente em outro conjunto de resultados tabulados. Mas o caso geral de "exclusão" aqui é simplesmente modificar a consulta para excluir os "vencedores" dos possíveis resultados.

No entanto, na verdade, demonstrarei "quebrar um loop" pelo menos em um sentido "moderno", mesmo que você não precise disso para o que realmente precisa fazer aqui, que é, na verdade, modificar a consulta para excluir.
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, claro, com qualquer abordagem, os resultados são aleatórios a cada vez e os "vencedores" anteriores são excluídos da seleção na própria consulta. O "loop break" aqui é usado apenas para continuar produzindo resultados até que não haja mais vencedores possíveis.

Uma observação sobre os métodos de "quebra de loop"


A recomendação geral em ambientes node.js modernos seria o async/await/yield integrado recursos agora incluídos como ativados por padrão nas versões v8.x.x. Essas versões chegarão ao Long Term Support (LTS) em outubro deste ano (no momento da redação) e seguindo minha própria "regra de três meses" pessoal, então quaisquer novos trabalhos devem ser baseados em coisas que seriam atuais naquele momento.

Os casos alternativos aqui são apresentados via async.await como uma dependência de biblioteca separada. Ou como uma dependência de biblioteca separada usando "Bluebird" Promise.coroutine , com o último caso sendo que você pode usar alternadamente Promise.try , mas se você for incluir uma biblioteca para obter essa função, poderá usar a outra função que implementa a abordagem de sintaxe mais moderna.

Então, "enquanto" (trocadilho não intencional) demonstrando "quebrando uma promessa/retorno de chamada" loop, a principal coisa que realmente deve ser tirada daqui é o processo de consulta diferente, que realmente faz a "exclusão" que estava sendo tentada ser implementada em um "loop" até que o vencedor aleatório fosse selecionado.

O caso real é que os dados determinam isso melhor. Mas todo o exemplo pelo menos mostra maneiras de aplicar "ambos" a seleção e a "quebra de loop".