In breve, in questo caso non è necessario farlo. Ma c'è una spiegazione più lunga.
Se la tua versione di MongoDB lo supporta, puoi semplicemente usare $sample
pipeline di aggregazione dopo le condizioni della query iniziale per ottenere la selezione "casuale".
Naturalmente, in ogni caso, se qualcuno non è idoneo perché ha già "vinto", contrassegnalo semplicemente come tale, o direttamente in un'altra serie di risultati tabulati. Ma il caso generale di "esclusione" qui è semplicemente modificare la query per escludere i "vincitori" da possibili risultati.
Tuttavia, in realtà dimostrerò "interrompere un ciclo" almeno in un senso "moderno" anche se in realtà non ne hai bisogno per ciò che devi effettivamente fare qui, che in realtà è modificare invece la query da escludere.
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()
}
})()
E ovviamente con entrambi gli approcci i risultati sono casuali ogni volta e i precedenti "vincitori" sono esclusi dalla selezione nella query stessa. L'"interruzione del ciclo" qui viene semplicemente utilizzata per continuare a produrre risultati fino a quando non ci saranno più possibili vincitori.
Una nota sui metodi di "interruzione del ciclo"
La raccomandazione generale nei moderni ambienti node.js sarebbe quella incorporata in async/await/yield
funzionalità ora incluse come attivate per impostazione predefinita nelle versioni v8.x.x. Queste versioni raggiungeranno il supporto a lungo termine (LTS) nell'ottobre di quest'anno (al momento della stesura) e seguendo la mia personale "regola dei tre mesi", quindi qualsiasi nuovo lavoro dovrebbe essere basato su cose che sarebbero attuali in quel momento.
I casi alternativi qui sono presentati tramite async.await
come una dipendenza dalla libreria separata. O altrimenti come una dipendenza dalla libreria separata utilizzando "Bluebird" Promise.coroutine
, con quest'ultimo caso in cui potresti alternativamente utilizzare Promise.try
, ma se hai intenzione di includere una libreria per ottenere quella funzione, allora potresti anche usare l'altra funzione che implementa l'approccio sintattico più moderno.
Quindi "mentre" (gioco di parole non inteso) dimostrando "infrangere una promessa/richiamare" loop, la cosa principale che dovrebbe davvero essere tolta da qui è il diverso processo di query, che in realtà esegue l'"esclusione" che si stava tentando di implementare in un "ciclo" fino a quando non è stato selezionato il vincitore casuale.
Il caso reale è che i dati lo determinano meglio. Ma l'intero esempio mostra almeno i modi in cui "sia" la selezione che l'"interruzione del ciclo" possono essere applicate.