Накратко, всъщност не е необходимо да правите това в този случай. Но има по-дълго обяснение.
Ако вашата версия на MongoDB го поддържа, можете просто да използвате $sample
тръбопровод за агрегиране след първоначалните ви условия на заявка, за да получите „случайния“ избор.
Разбира се, във всеки случай, ако някой не отговаря на условията, защото вече е „спечелил“, тогава просто го маркирайте като такъв, или директно в друг набор от таблични резултати. Но общият случай на „изключване“ тук е просто да промените заявката, за да изключите „победителите“ от възможните резултати.
Въпреки това, аз всъщност ще демонстрирам "разбиване на цикъл" поне в "модерен" смисъл, въпреки че всъщност не се нуждаете от това за това, което всъщност трябва да направите тук, което всъщност е да промените заявката, за да изключите вместо това.
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()
}
})()
И, разбира се, при двата подхода резултатите са произволни всеки път и предишните „победители“ се изключват от избора в самата заявка. „Прекъсването на цикъла“ тук се използва само за продължаване на извеждането на резултати, докато не изчезнат възможните победители.
Бележка относно методите за "разбиване на цикъл"
Общата препоръка в съвременните среди на node.js би била вграденият async/await/yield
функции, които вече са включени като включени по подразбиране във версии v8.x.x. Тези версии ще достигнат дългосрочна поддръжка (LTS) през октомври тази година (към момента на писане) и според моето лично „правило за три месеца“, тогава всички нови произведения трябва да се основават на неща, които биха били актуални към този момент.
Алтернативните случаи тук са представени чрез async.await
като отделна зависимост от библиотеката. Или по друг начин като отделна зависимост от библиотека с помощта на "Bluebird" Promise.coroutineкод>
, като последният случай е, че можете алтернативно да използвате Promise.tryкод>
, но ако ще включите библиотека, за да получите тази функция, тогава можете също да използвате другата функция, която прилага по-модерния синтаксисен подход.
Така че „докато“ (игра на думи не е предназначена), демонстрираща „нарушаване на обещание/обратно повикване“ цикъл, основното нещо, което наистина трябва да бъде премахнато от тук, е различният процес на заявка, който всъщност прави „изключването“, което се опитваше да бъде приложено в „цикл“, докато не бъде избран произволният победител.
Действителният случай е, че данните определят това най-добре. Но целият пример поне показва начини, по които могат да бъдат приложени "както" селекцията, така и "прекъсването на цикъла".