sql >> Base de Datos >  >> NoSQL >> MongoDB

¿Cómo reinicio condicionalmente la cadena de promesas desde el principio?

En resumen, en realidad no necesita hacer eso en este caso. Pero hay una explicación más larga.

Si su versión de MongoDB lo admite, simplemente puede usar el $sample tubería de agregación después de las condiciones de consulta iniciales para obtener la selección "aleatoria".

Por supuesto, en cualquier caso, si alguien no es elegible porque ya "ganó", simplemente márquelo como tal, ya sea directamente en otro conjunto de resultados tabulados. Pero el caso general de "exclusión" aquí es simplemente modificar la consulta para excluir a los "ganadores" de los posibles resultados.

Sin embargo, en realidad demostraré "romper un ciclo" al menos en un sentido "moderno", aunque en realidad no lo necesita para lo que realmente debe hacer aquí, que en realidad es modificar la consulta para excluirla.

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

Y, por supuesto, con cualquier enfoque, los resultados son aleatorios cada vez y los "ganadores" anteriores se excluyen de la selección en la consulta real. El "bucle de interrupción" aquí se usa simplemente para seguir generando resultados hasta que no haya más posibles ganadores.

Una nota sobre los métodos de "romper bucles"

La recomendación general en los entornos modernos de node.js sería el async/await/yield integrado. las funciones ahora se incluyen como activadas de forma predeterminada en las versiones v8.x.x. Estas versiones llegarán al soporte a largo plazo (LTS) en octubre de este año (al momento de escribir este artículo) y siguiendo mi propia "regla de tres meses", cualquier trabajo nuevo debe basarse en cosas que estarían actualizadas en ese momento.

Los casos alternativos aquí se presentan a través de async.await como una dependencia de biblioteca independiente. O de lo contrario, como una dependencia de biblioteca separada usando "Bluebird" Promise.coroutine , en el último caso, podría usar alternativamente Promise.try , pero si va a incluir una biblioteca para obtener esa función, también podría usar la otra función que implementa el enfoque de sintaxis más moderno.

Así que "mientras" (juego de palabras no pretendido) demuestra "romper una promesa/devolución de llamada" bucle, lo principal que realmente debería quitarse de aquí es el proceso de consulta diferente, que en realidad hace la "exclusión" que se intentaba implementar en un "bucle" hasta que se seleccionaba al ganador aleatorio.

El caso real es que los datos determinan esto mejor. Pero el ejemplo completo al menos muestra formas en que se pueden aplicar "tanto" la selección como el "salto de bucle".