sql >> Databáze >  >> NoSQL >> MongoDB

Jak podmíněně restartuji řetězec slibů od začátku?

Stručně řečeno, v tomto případě to ve skutečnosti nemusíte dělat. Existuje však delší vysvětlení.

Pokud to vaše verze MongoDB podporuje, můžete jednoduše použít $sample agregační kanál po vašich počátečních podmínkách dotazu, abyste získali „náhodný“ výběr.

Samozřejmě v každém případě, pokud někdo není způsobilý, protože již „vyhrál“, jednoduše ho jako takového označte, buď přímo v jiné sadě tabulkových výsledků. Obecným případem „vyloučení“ je však jednoduše upravit dotaz tak, aby z možných výsledků vyloučil „vítěze“.

Ve skutečnosti však předvedu „prolomení smyčky“ alespoň v „moderním“ smyslu, i když to ve skutečnosti nepotřebujete k tomu, co zde skutečně potřebujete udělat, což je ve skutečnosti upravit dotaz tak, aby se místo toho vyloučil.

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

A samozřejmě s oběma přístupy jsou výsledky pokaždé náhodné a předchozí "vítězové" jsou vyloučeni z výběru v samotném dotazu. "Přerušení smyčky" zde slouží pouze k udržení výsledků, dokud nebudou žádní další možní vítězové.

Poznámka k metodám "přerušení smyčky"

Obecným doporučením v moderních prostředích node.js by bylo vestavěné async/await/yield funkce jsou nyní ve verzích v8.x.x zahrnuty jako standardně zapnuté. Tyto verze se dostanou na dlouhodobou podporu (LTS) v říjnu tohoto roku (v době psaní tohoto článku) a podle mého osobního „tříměsíčního pravidla“ by jakákoli nová díla měla být založena na věcech, které by byly aktuální v daném okamžiku.

Alternativní případy jsou zde uvedeny prostřednictvím async.await jako samostatná knihovna. Nebo jinak jako samostatná knihovna pomocí "Bluebird" Promise.coroutine , přičemž v druhém případě můžete alternativně použít Promise.try , ale pokud se chystáte zahrnout knihovnu, abyste získali tuto funkci, pak můžete také použít jinou funkci, která implementuje modernější přístup k syntaxi.

Takže „zatímco“ (nezamýšlená slovní hříčka) demonstruje „porušení slibu/zpětné zavolání“ smyčka, hlavní věc, která by se odsud skutečně měla odstranit, je odlišný proces dotazování, který ve skutečnosti provádí „vyloučení“, které bylo pokoušeno implementovat do „smyčky“, dokud nebyl vybrán náhodný vítěz.

Ve skutečnosti to nejlépe určují data. Ale celý příklad alespoň ukazuje způsoby, jak lze použít „jak“ výběr, tak „přerušení smyčky“.




  1. Index fulltextového vyhledávání MongoDB:chyba:příliš mnoho textového indexu pro, proč?

  2. Jak používat proměnné s MongoDB $lookup

  3. Agregační dotaz MongoDB k počítání

  4. Jak převést datum na UTC v MongoMapper &Ruby/Rails?