Kort sagt, du behöver faktiskt inte göra det i det här fallet. Men det finns en längre förklaring.
Om din MongoDB-version stöder det kan du helt enkelt använda $sample
aggregeringspipeline efter dina initiala frågevillkor för att få det "slumpmässiga" valet.
Naturligtvis i alla fall, om någon inte är kvalificerad för att de redan "vunnit" så markera dem helt enkelt som sådana, antingen direkt i en annan uppsättning av tabellerade resultat. Men det allmänna fallet med "uteslutning" här är att helt enkelt modifiera frågan för att utesluta "vinnarna" från möjliga resultat.
Men jag kommer faktiskt att demonstrera "bryta en loop" åtminstone i en "modern" mening även om du faktiskt inte behöver det för vad du faktiskt behöver göra här, vilket faktiskt är att modifiera frågan för att utesluta istället.
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()
}
})()
Och naturligtvis med båda tillvägagångssätten är resultaten slumpmässiga varje gång och tidigare "vinnare" exkluderas från urvalet i själva frågan. "Slingbrytningen" här används bara för att fortsätta visa resultat tills det inte finns fler möjliga vinnare.
En notering om metoderna för "loopbrytning"
Den allmänna rekommendationen i moderna node.js-miljöer skulle vara den inbyggda async/await/yield
funktioner som nu ingår som aktiverade som standard i v8.x.x-versioner. Dessa versioner kommer att träffa Long Term Support (LTS) i oktober i år (i skrivande stund) och enligt min egen personliga "tremånadersregel", så bör alla nya verk baseras på saker som skulle vara aktuella vid den tidpunkten.
De alternativa fallen här presenteras via async.await
som ett separat biblioteksberoende. Eller på annat sätt som ett separat biblioteksberoende med "Bluebird" Promise.coroutine
, där det senare fallet är att du alternativt kan använda Promise.try
, men om du ska inkludera ett bibliotek för att få den funktionen kan du lika gärna använda den andra funktionen som implementerar den mer moderna syntaxmetoden.
Så "medan" ( ordlek inte avsedd ) demonstrerar "bryter ett löfte/återuppringning" loop, det viktigaste som egentligen borde tas bort härifrån är den annorlunda frågeprocessen, som faktiskt gör "uteslutningen" som försökte implementeras i en "loop" tills den slumpmässiga vinnaren utsågs.
Det faktiska fallet är att data bestämmer detta bäst. Men hela exemplet visar åtminstone hur "både" valet och "loop break" kan tillämpas.