Javascript 如何从一开始就有条件地重新启动承诺链?

Javascript 如何从一开始就有条件地重新启动承诺链?,javascript,node.js,mongodb,promise,mongodb-query,Javascript,Node.js,Mongodb,Promise,Mongodb Query,我正在尝试实现一个简单的抽奖系统,在这个系统中,我执行一个GET/test,返回一个随机用户,该用户(1)以前没有赢得过抽奖,(2)在过去一小时内注册过 在Mongo中,可能有多个文档与用户关联,因为用户可以注册多个主题。例如,{id:1,名称:'John',主题:'math',…}和{id:1,名称:'John',主题:'english',…}。如果约翰因为数学而被选入抽奖,那么他将没有资格参加随后的所有抽奖,因此他不能多次获奖。本质上,抽奖获胜者的id必须是唯一的 我的问题是:我如何做的逻辑

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

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

我的问题是:我如何做的逻辑检查,如果约翰以前赢了?如果John已经赢了,我想从顶部重新启动承诺链,并再次执行
数学.random
,直到选出唯一的赢家。如果没有符合条件的获胜者,那么我想返回
res.status(500)


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

如果您的MongoDB版本支持它,那么您只需在初始查询条件之后使用聚合管道即可获得“随机”选择

当然,在任何情况下,如果有人因为已经“赢了”而没有资格,那么就直接在另一组列表结果中标记他们。但是这里“排除”的一般情况是简单地修改查询,从可能的结果中排除“赢家”

然而,我将实际演示至少在“现代”意义上的“打破循环”,即使您实际上不需要这样做,这实际上是修改查询以排除

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),那么任何新作品都应该基于当时的流行内容

这里的备选案例通过作为单独的库依赖项呈现。或者使用“Bluebird”作为单独的库依赖项,后一种情况是您可以交替使用,但是如果您要包含一个库来获取该函数,那么您也可以使用实现更现代语法方法的另一个函数

因此,“虽然”(并非有意双关语)演示了“打破承诺/回调”循环,但这里真正应该去掉的主要内容是不同的查询过程,它实际上在“循环”中执行“排除”,直到随机获胜者被选中


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

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

如果您的MongoDB版本支持它,那么您只需在初始查询条件之后使用聚合管道即可获得“随机”选择

当然,在任何情况下,如果有人因为已经“赢了”而没有资格,那么就直接在另一组列表结果中标记他们。但是这里“排除”的一般情况是简单地修改查询,从可能的结果中排除“赢家”

然而,我将实际演示至少在“现代”意义上的“打破循环”,即使您实际上不需要这样做,这实际上是修改查询以排除

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),那么任何新作品都应该基于当时的流行内容

这里的备选案例通过作为单独的库依赖项呈现。或者使用“Bluebird”作为单独的库依赖项,后一种情况是您可以交替使用,但是如果您要包含一个库来获取该函数,那么您也可以使用实现更现代语法方法的另一个函数

因此,“虽然”(并非有意双关语)演示了“打破承诺/回调”循环,但这里真正应该去掉的主要内容是不同的查询过程,它实际上在“循环”中执行“排除”,直到随机获胜者被选中


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

一个想法:如果不管主题如何更新获奖者id的所有记录,那么原始查询将永远不会返回不合格的记录提供的答案中是否有您认为无法解决问题的内容?如果是,请对c的答案发表评论