【问题标题】:How do I conditionally restart the promise chain from the beginning?我如何有条件地从头开始重新启动承诺链?
【发布时间】:2017-12-20 17:59:49
【问题描述】:

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

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

我的问题是:我如何执行检查 John 之前是否赢过的逻辑?如果约翰已经赢了,我想从顶部重新启动承诺链并再次执行Math.random,直到选出唯一的赢家。如果没有获胜者符合条件,那么我想返回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);
            });
        }
    })

【问题讨论】:

  • 一个想法:如果您更新 所有 获胜者 ID 的记录而不考虑主题,那么原始查询将永远不会返回不合格的记录
  • 您认为提供的答案中是否有某些内容无法解决您的问题?如果是这样,请对答案发表评论,以澄清究竟需要解决哪些尚未解决的问题。如果它确实回答了您提出的问题,请注意Accept your Answers您提出的问题

标签: javascript node.js mongodb promise mongodb-query


【解决方案1】:

简而言之,在这种情况下,您实际上不需要这样做。但还有更长的解释。

如果您的 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 版本中默认启用。这些版本将在今年 10 月(截至发稿时)推出长期支持 (LTS),并遵循我个人的“三个月规则”,那么任何新作品都应基于当时的最新内容。

这里的替代案例通过async.await 呈现为单独的库依赖项。或者使用“Bluebird”Promise.coroutine 作为单独的库依赖项,后一种情况是您可以交替使用Promise.try,但如果您要包含一个库来获取该功能,那么您不妨使用其他实现更现代语法方法的函数。

所以“虽然”(双关语不是有意的)演示“打破承诺/回调”循环,真正应该从这里带走的主要是不同的查询过程,它实际上做了试图在“循环”中实施的“排除”,直到随机获胜者被选中。

实际情况是数据决定了这一点。但整个示例至少显示了可以“同时”应用选择和“循环中断”的方式。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-04-09
    • 2016-01-07
    • 1970-01-01
    • 1970-01-01
    • 2018-02-25
    • 2016-04-25
    • 2019-11-19
    相关资源
    最近更新 更多