Em resumo, você realmente não precisa fazer isso neste caso. Mas há uma explicação mais longa.
Se sua versão do MongoDB for compatível, você pode simplesmente usar o
$amostra
pipeline de agregação após suas condições de consulta iniciais para obter a seleção "aleatória". É claro que, em qualquer caso, se alguém não for elegível porque já "ganhou", basta marcá-lo como tal, diretamente em outro conjunto de resultados tabulados. Mas o caso geral de "exclusão" aqui é simplesmente modificar a consulta para excluir os "vencedores" dos possíveis resultados.
No entanto, na verdade, demonstrarei "quebrar um loop" pelo menos em um sentido "moderno", mesmo que você não precise disso para o que realmente precisa fazer aqui, que é, na verdade, modificar a consulta para excluir.
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, claro, com qualquer abordagem, os resultados são aleatórios a cada vez e os "vencedores" anteriores são excluídos da seleção na própria consulta. O "loop break" aqui é usado apenas para continuar produzindo resultados até que não haja mais vencedores possíveis.
Uma observação sobre os métodos de "quebra de loop"
A recomendação geral em ambientes node.js modernos seria o
async/await/yield
integrado recursos agora incluídos como ativados por padrão nas versões v8.x.x. Essas versões chegarão ao Long Term Support (LTS) em outubro deste ano (no momento da redação) e seguindo minha própria "regra de três meses" pessoal, então quaisquer novos trabalhos devem ser baseados em coisas que seriam atuais naquele momento. Os casos alternativos aqui são apresentados via
async.await
como uma dependência de biblioteca separada. Ou como uma dependência de biblioteca separada usando "Bluebird" Promise.coroutine
, com o último caso sendo que você pode usar alternadamente Promise.try
, mas se você for incluir uma biblioteca para obter essa função, poderá usar a outra função que implementa a abordagem de sintaxe mais moderna. Então, "enquanto" (trocadilho não intencional) demonstrando "quebrando uma promessa/retorno de chamada" loop, a principal coisa que realmente deve ser tirada daqui é o processo de consulta diferente, que realmente faz a "exclusão" que estava sendo tentada ser implementada em um "loop" até que o vencedor aleatório fosse selecionado.
O caso real é que os dados determinam isso melhor. Mas todo o exemplo pelo menos mostra maneiras de aplicar "ambos" a seleção e a "quebra de loop".