Krótko mówiąc, w tym przypadku nie musisz tego robić. Ale jest dłuższe wyjaśnienie.
Jeśli Twoja wersja MongoDB to obsługuje, możesz po prostu użyć $przykład
potok agregacji po początkowych warunkach zapytania w celu uzyskania „losowego” wyboru.
Oczywiście w każdym przypadku, jeśli ktoś nie kwalifikuje się ponieważ już "wygrał" to po prostu oznacz go jako takiego, albo bezpośrednio w innym zestawie wyników tabelarycznych. Ale ogólnym przypadkiem „wykluczenia” jest tutaj po prostu zmodyfikowanie zapytania, aby wykluczyć „zwycięzców” z możliwych wyników.
Jednak faktycznie zademonstruję „przerywanie pętli” przynajmniej w „nowoczesnym” sensie, mimo że tak naprawdę nie potrzebujesz tego do tego, co faktycznie musisz tutaj zrobić, czyli w rzeczywistości zmodyfikuj zapytanie, aby zamiast tego wykluczyć.
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()
}
})()
I oczywiście przy każdym podejściu wyniki są za każdym razem losowe, a poprzedni „zwycięzcy” są wykluczani z wyboru w samym zapytaniu. „Przerwa w pętli” jest tutaj używana jedynie do utrzymywania wyników, dopóki nie będzie więcej możliwych zwycięzców.
Uwaga na temat metod „przerywania pętli”
Ogólne zalecenie w nowoczesnych środowiskach node.js to wbudowany async/await/yield
funkcje są teraz włączone jako domyślnie włączone w wersjach v8.x.x. Te wersje trafią do Long Term Support (LTS) w październiku tego roku (od momentu pisania) i będą podlegać mojej osobistej „zasadzie trzech miesięcy”, więc wszelkie nowe prace powinny opierać się na rzeczach, które byłyby aktualne w tym momencie.
Alternatywne przypadki tutaj są przedstawione za pośrednictwem async.await
jako oddzielna zależność biblioteki. Lub inaczej jako oddzielna zależność biblioteki przy użyciu „Bluebird” Promise.coroutine
, przy czym w drugim przypadku można naprzemiennie używać Promise.try
, ale jeśli zamierzasz dołączyć bibliotekę, aby uzyskać tę funkcję, możesz równie dobrze użyć innej funkcji, która implementuje bardziej nowoczesne podejście do składni.
Tak więc „podczas” (gra słów niezamierzona) demonstruje „złamanie obietnicy/oddzwanianie” pętla, najważniejszą rzeczą, którą naprawdę należy tutaj odjąć, jest inny proces zapytania, który faktycznie wykonuje „wykluczenie”, które próbowano zaimplementować w „pętli”, dopóki nie zostanie wybrany losowy zwycięzca.
Rzeczywisty przypadek jest taki, że najlepiej to określają dane. Ale cały przykład pokazuje przynajmniej, w jaki sposób można zastosować „zarówno” zaznaczenie, jak i „przerwanie pętli”.