En bref, vous n'avez pas vraiment besoin de le faire dans ce cas. Mais il y a une explication plus longue.
Si votre version de MongoDB le prend en charge, vous pouvez simplement utiliser le $sample
pipeline d'agrégation après vos conditions de requête initiales afin d'obtenir la sélection "aléatoire".
Bien sûr, dans tous les cas, si quelqu'un n'est pas éligible parce qu'il a déjà "gagné", il suffit de le marquer comme tel, soit directement dans un autre ensemble de résultats tabulés. Mais le cas général "d'exclusion" ici consiste simplement à modifier la requête pour exclure les "gagnants" des résultats possibles.
Cependant, je vais en fait démontrer "casser une boucle" au moins dans un sens "moderne" même si vous n'en avez pas réellement besoin pour ce que vous devez réellement faire ici, c'est-à-dire modifier la requête pour exclure à la place.
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()
}
})()
Et bien sûr, avec l'une ou l'autre approche, les résultats sont aléatoires à chaque fois et les "gagnants" précédents sont exclus de la sélection dans la requête elle-même. La « rupture de boucle » ici est simplement utilisée pour continuer à produire des résultats jusqu'à ce qu'il n'y ait plus de gagnants possibles.
Une note sur les méthodes de « rupture de boucle »
La recommandation générale dans les environnements node.js modernes serait le async/await/yield
intégré fonctionnalités désormais incluses comme activées par défaut dans les versions v8.x.x. Ces versions arriveront sur le support à long terme (LTS) en octobre de cette année (au moment de la rédaction) et selon ma propre "règle des trois mois", alors tout nouveau travail devrait être basé sur des choses qui seraient d'actualité à ce moment-là.
Les cas alternatifs ici sont présentés via async.await
en tant que dépendance de bibliothèque distincte. Ou sinon en tant que dépendance de bibliothèque distincte en utilisant "Bluebird" Promise.coroutine
, ce dernier cas étant que vous pouvez également utiliser Promise.try
, mais si vous allez inclure une bibliothèque pour obtenir cette fonction, vous pouvez aussi bien utiliser l'autre fonction qui implémente l'approche de syntaxe plus moderne.
Donc "alors que" (jeu de mots non intentionnel) démontrant "rompre une promesse/un rappel" boucle, la principale chose qui devrait vraiment être retirée d'ici est le processus de requête différent, qui effectue en fait "l'exclusion" qui a été tentée d'être implémentée dans une "boucle" jusqu'à ce que le gagnant aléatoire soit sélectionné.
Le cas réel est que les données le déterminent le mieux. Mais l'ensemble de l'exemple montre au moins comment "à la fois" la sélection et la "rupture de boucle" peuvent être appliquées.