如何从一开始有条件地重新启动Promise链? [英] How do I conditionally restart the promise chain from the beginning?

查看:77
本文介绍了如何从一开始有条件地重新启动Promise链?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试实现一个简单的抽奖系统,在该系统中,我执行GET/test来返回一个随机用户,该用户(1)之前未赢得抽奖,并且(2)在过去一个小时内注册.

I am trying to implement a simple raffling system where I do a GET /test which returns a random user who (1) hasn't won the raffle previously and (2)registered in the past hour.

在Mongo中,由于用户可以注册多个主题,因此可能有多个与该用户关联的文档.例如{id: 1, name: 'John', subject: 'math',...}{id: 1, name: 'John', subject: 'english',...}.如果约翰为数学抽奖而被选中,那么他将没有资格参加随后的所有抽奖活动,因此他无法多次获胜.从本质上讲,抽奖获胜者的ID必须是唯一的.

In Mongo, there may be multiple documents associated with a user since the user can sign up for multiple subjects. For example {id: 1, name: 'John', subject: 'math',...} and {id: 1, name: 'John', subject: 'english',...}. If John gets picked for the raffle for math, then he will be ineligible for all subsequent raffles, so he can't get win multiple times. Essentially, the id of the raffle winner must be unique.

我的问题是:我该如何检查约翰以前是否赢过的逻辑?如果约翰已经赢了,我想从头开始重新开始诺言链,然后再次执行Math.random,直到选择唯一的赢家为止.如果没有获奖者符合条件,那么我想返回res.status(500).

My question is: how do I do the logic for checking if John has won previously? If John has won already, I want to re-start the promise chain from the top and do a Math.random again until a unique winner is picked. If no winner is eligible, then I want to return a res.status(500).

   app.get('/test', function(req, res, next) {
        var currentTimestamp = new Date()
        var oneHourAgo = new Date(currentTimestamp - oneHour)
        var query = { "timeStamp": { $lte: currentTimestamp, $gte: oneHourAgo }, "isWinner": false }
        var winna = {}
        var winnerSelected = false

        var collection = db.collection('entries');

        while (!winnerSelected) {  // how can I do this
            collection.find(query).toArray().then( result => {
                var winner = result[Math.floor(Math.random() * result.length)];
                var query2     = {"id" : winner.id};
                winna['id'] = winner.id
                winna['name'] = winner.name
                winna['subject'] = winner.subject
                winna['timeStamp'] = winner.timeStamp
                winna['isWinner'] = winner.isWinner
                winna['raffleTimestamp'] = winner.raffleTimestamp

                return collection.find(query2).toArray();
            }).then( result => {
                for (var i in result) { // a winner can enter the raffle for multiple subjects, but if he already won once, then I want to redraw by doing a rand function again 
                    if (i.isWinner) {    // until a winner who is eligible is found, or if none are eligible, res.status(500)
                        console.log("i already won")
                        break
                        // how do I make it go back to the beginning of the while loop and pick a new random winner?
                    }
                }

                console.log("unique winner")
                winnerSelected = true // break out of while loop
                var query3 = { id: winna.id, subject: winna.subject }
                var raffleTimestamp = new Date()
                var update = { $set: { isWinner: true, raffleTimestamp: raffleTimestamp } }
                winna['isWinner'] = true
                winna['raffleTimestamp'] = raffleTimestamp
                res.send(winna) // send the winner with the updated fields to clientside
                return collection.updateOne(query3, update); // update the isWinner and raffleTimestamp fields
            }).then( result => {
             res.status(200);
            // res.send(result);
            }).catch( err => {
                console.log(err);
                res.status(500);
            });
        }
    })

推荐答案

总之,在这种情况下,您实际上不需要这样做.但是有更长的解释.

In brief, you don't actually need to do that in this case. But there is a longer explanation.

如果您的MongoDB版本支持它,则只需使用 $sample 初始查询条件后的聚合管道,以便获得随机"选择.

If your MongoDB version supports it, then you could simply use the $sample aggregation pipeline after your initial query conditions in order to get the "random" selection.

当然,在任何情况下,如果某人因为已经胜出"而没有资格,则只需将其标记为这样,就可以直接在另一组列表结果中进行标记.但是,此处排除"的一般情况是简单地修改查询,以将优胜者"从可能的结果中排除.

Of course in any case, if someone is not eligible because they already "won" then simply mark them as such, either directly on in another set of tabulated results. But the general case of "exclusion" here is to simply modify the query to exclude the "winners" from possible results.

但是,我实际上将至少在现代"意义上展示打破循环",即使您实际上不需要在这里实际要做的事情也是如此,实际上是修改查询以排除.

However, I will actually demonstrate "breaking a loop" at least in a "modern" sense even though you do not actually need that for what you actually need to do here, which is actually modify the query to exclude instead.

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()
  }
})()

当然,无论采用哪种方法,每次的结果都是随机的,并且在实际查询本身中从选择中排除以前的优胜者".这里的循环中断"仅用于保持输出结果,直到没有更多可能的获胜者为止.

And of course with either approach the results are random each time and previous "winners" are excluded from selection in the actual query itself. The "loop break" here is merely used to keep outputting results until there can be no more possible winners.

现代的node.js环境中的一般建议是内置的async/await/yield功能,现在已包含在v8.x.x版本中默认情况下处于启用状态.这些版本将在今年10月达到长期支持(LTS)(截至撰写时),并按照我自己的三个月规则"进行,然后任何新作品都应基于当时的最新情况.

The general recommendation in modern node.js environments would to be the built in async/await/yield features now included as turned on by default in v8.x.x releases. These versions will hit Long Term Support (LTS) in October this year ( as of writing ) and going by my own personal "three month rule", then any new works should be based on things that would be current at that point in time.

此处的替代情况通过 async.await 单独显示库依赖.或者使用"Bluebird" Promise.coroutine 作为单独的库依赖项后一种情况是您可以交替使用 Promise.try ,但是如果您将要包含一个库来获取该函数,那么您最好使用实现更现代语法方法的另一个函数.

The alternate cases here are presented via async.await as a separate library dependency. Or otherwise as a separate library dependency using "Bluebird" Promise.coroutine, with the latter case being that you could alternately use Promise.try, but if you are going to include a library to get that function, then you might as well use the other function which implements the more modern syntax approach.

因此,"whilst"(双关语是无意的)演示了违反承诺/回调" 循环,实际上应该从这里带走的主要事情是不同的查询过程,该过程实际上是试图以循环"方式实施排除",直到选择了随机获胜者为止.

So "whilst" ( pun not intended ) demonstrating "breaking a promise/callback" loop, the main thing that really should be taken away from here is the different query process, which actually does the "exclusion" that was being attempted to be implemented in a "loop" until the random winner was selected.

实际情况是数据确定了最佳结果.但是整个示例至少显示了可以同时应用选择和循环中断"的方式.

The actual case is the data determines this best. But the whole example does at least show ways that "both" the selection and the "loop break" can be applied.

这篇关于如何从一开始有条件地重新启动Promise链?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆