Kort sagt, du behøver faktisk ikke at gøre det i dette tilfælde. Men der er en længere forklaring.
Hvis din MongoDB-version understøtter det, kan du blot bruge $sample
aggregeringspipeline efter dine indledende forespørgselsbetingelser for at få det "tilfældige" valg.
Selvfølgelig under alle omstændigheder, hvis nogen ikke er berettiget, fordi de allerede har "vundet", skal du blot markere dem som sådan, enten direkte på i et andet sæt af tabelresultater. Men det generelle tilfælde af "udelukkelse" her er blot at ændre forespørgslen for at udelukke "vinderne" fra mulige resultater.
Men jeg vil faktisk demonstrere "breaking a loop" i det mindste i en "moderne" forstand, selvom du faktisk ikke har brug for det for det, du faktisk skal gøre her, som faktisk er at ændre forespørgslen til at ekskludere i stedet.
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()
}
})()
Og selvfølgelig med begge tilgange er resultaterne tilfældige hver gang, og tidligere "vindere" er udelukket fra udvælgelsen i selve forespørgslen. "loop break" her bruges blot til at blive ved med at udskrive resultater, indtil der ikke kan være flere mulige vindere.
En note om "loop breaking"-metoderne
Den generelle anbefaling i moderne node.js-miljøer ville være den indbyggede async/await/yield
funktioner inkluderet nu som aktiveret som standard i v8.x.x-udgivelser. Disse versioner vil ramme Long Term Support (LTS) i oktober i år (i skrivende stund) og i henhold til min egen personlige "tre måneders regel", så bør alle nye værker være baseret på ting, der ville være aktuelle på det tidspunkt.
De alternative tilfælde her præsenteres via async.await
som en separat biblioteksafhængighed. Eller på anden måde som en separat biblioteksafhængighed ved hjælp af "Bluebird" Promise.coroutine
, hvor sidstnævnte tilfælde er, at du alternativt kunne bruge Promise.try
, men hvis du vil inkludere et bibliotek for at få den funktion, så kan du lige så godt bruge den anden funktion, som implementerer den mere moderne syntakstilgang.
Så "mens" (ordspil ikke tilsigtet) demonstrerer "at bryde et løfte/tilbagekald" loop, det vigtigste, der virkelig bør tages væk herfra, er den anderledes forespørgselsproces, som faktisk udfører den "udelukkelse", som blev forsøgt implementeret i en "loop", indtil den tilfældige vinder blev valgt.
Det faktiske tilfælde er, at dataene bestemmer dette bedst. Men hele eksemplet viser i det mindste måder, hvorpå "både" markeringen og "loop break" kan anvendes.