使用async while循环创建和插入文档

时间:2017-07-20 00:33:17

标签: javascript node.js mongodb mongoose promise

我试图动态生成令牌并将其保存到数据库中。

这是生成令牌的代码。

const generateToken = function (maxUse) {
  // 12 digit token numbers. 9e+11 possibilities
  const min = 100000000000;
  const max = 999999999999;
  const token =  Math.floor(Math.random() * (max -min) + min);
  // ensure token doesn't exit exist in db before saving
  Token.count({ token }, function (err, count) {
    if (count > 0) {
      generateToken() ;
    } else {
      let newToken = new Token({ token, maxUse });
      newToken.save(function (err, savedToken) {
        if (err) {
          console.log(err);
          return;
        } else {
          generateSerial(savedToken._id);
          console.log("saved token is =>", savedToken.token);
          return savedToken.token;
        }
      })
    }
  })
}

如何编写一个任意次调用此函数的函数,将标记附加到文件中,因为它们被保存到数据库中。我意识到由于过程的异步性,while循环不会起作用。

我所看到的所有答案都假设我提前有大量数据,例如使用bulkwrite(mongoose)。

欢迎另一种方法

谢谢。

1 个答案:

答案 0 :(得分:1)

我看到这一点的方式,你可能最好保留一个"本地列表"生成的令牌和"批量"通过.insertMany()插入。里程可能因实际实现而异,因此我们将讨论该方法以及使用异步方法以合理的方式处理递归函数。

异步循环

您已经创建了一个问题,您需要测试存在的值,以确定它们是"唯一的"用于插入。这当然需要异步调用以查看数据库,因此排除"批量"诸如" upserts"等行为因为在循环发送之前你不知道该项是否存在。因此递归确实适用于这种情况。

所以你要做的第一件事就是制作"功能"异步本身,要么返回回调,要么返回承诺。

本质上:

function generateToken(maxUse) {
  const min = 100000000000;
  const max = 999999999999;
  const token =  Math.floor(Math.random() * (max -min) + min);

  return Token.count({ token }).then( count => {

    if ( count > 0 ) {
      generateToken(maxUse);
    } else {
      return Token.create({ token, maxUse });
    }

  })
}

或者使用async / await更现代的术语

async function generateToken(maxUse) {
  const min = 100000000000;
  const max = 999999999999;
  const token =  Math.floor(Math.random() * (max -min) + min);

  let count = await Token.count({ token });

  if ( count > 0 ) {
    generateToken(maxUse);
  } else {
      return Token.create({ token, maxUse });
  }
}

然后,这只是一个循环调用的问题,无论是现代术语如下:

let count = 0;
while (count < 500) {
  // Random usage 1-5
  const maxUse = Math.floor(Math.random() * 5) + 1;
  let token = await generateToken(maxUse);
  log(token.token);
  count++;
}

如果在不支持async/await的节点版本下运行,则使用async.whilst

 asyncWhilst(
    () => count < 500,
    (callback) => {
      const maxUse = Math.floor(Math.random() * 5 ) + 1;
      generateToken(maxUse).then(token => {
        log(token.token);
        count++;
        callback();
      }).catch(err => callback(err));
    },
    (err) => {
      if (err) throw err;
      // Loop complete, issue callback or promise
    }
  );

所以这一切都相对简单。

保持唯一的本地和&#34;批量插入&#34;

&#34;替代&#34;处理此问题的方法是在客户端&#34;上保留生成的令牌数组。然后你需要在每个随机代上做的就是查看令牌是否已经被看到&#34;并且只在&#34; unique&#34;时才创建插入操作。获得了价值。

这应该比使用递归调用来回数据库快得多,因为它全部&#34;缓存&#34;本地。

从本质上讲,让你的发电机功能非常基本:

function generateToken(maxUse) {
  const min = 100000000000;
  const max = 999999999999;
  const token =  Math.floor(Math.random() * (max -min) + min);

  return ({ token, maxUse });
}

然后在循环期间,为seenTokensops创建两个数组,其中后者表示稍后插入&#34;批量批次&#34;而不是个人写:

let count = 0,
    seenTokens = [],
    ops = [];

while ( count < 500 ) {
  const maxUse = Math.floor(Math.random() * 5) + 1;

  let token = generateToken(maxUse);

  if ( seenTokens.indexOf(token.token) === -1 ) {
    seenTokens.push(token.token);
    ops.push(token);
    count++

    if ( count % 500 === 0 ) {
      await Token.insertMany(ops);
      ops = [];
    }
  } else {
    continue
  }

}

if ( count % 500 !== 0 ) {
  await Token.insertMany(ops);
  ops = [];
}

当然我们在那里应用了async/await方法,但这只是.insertMany()方法是异步的,如果你实际上没有插入&#34;成千上万#&#34 34;然后它应该很容易处理,甚至不需要等待&#34;等待&#34;这样的电话,然后只发出&#34;一次&#34;

但是这里的演示说明了当代码成千上万时代码应该是什么样子。没有其他改动。您可以再次使用其他库函数来等待&#34;等待&#34;这样的要求。

我们可以再次使用async.seriesasync.whilst进行此类控制:

  let count = 0,
    seenTokens = [],
    ops = [];

  asyncSeries(
    [
      (callback) =>
        asyncWhilst(
          () => count < 500,
          (callback) => {
            const maxUse = Math.floor(Math.random() * 5) + 1;

            let token = generateToken(maxUse);

            if ( seenTokens.indexOf(token.token) === -1 ) {
              seenTokens.push(token.token);
              ops.push(token);
              count++;

              if ( count % 500 === 0 ) {
                Token.insertMany(ops,(err,response) => {
                  console.log(count);
                  ops = [];
                  callback(err);
                });
              } else {
                callback();
              }
            } else {
              console.log("trying again: seen token %s", token.token);
              callback();
            }
          },
          callback
        ),

      (callback) => {
        if ( count % 500 !== 0 ) {
          Token.insertMany(ops,callback)
        } else {
          callback()
        }

      }
    ],
    (err) => {
      if (err) throw err;
      ops = [];
      // Operations complete, so callback to continue
    }
  );

一切都非常相同,而且&#34;流量控制&#34;实际上只是为了满足&#34;更大的批次&#34;,您可以简单地使用常规循环来构建ops条目,并且只对.insertMany()进行一次调用,就像{{ {1}}这里的限制确实存在。

所以最简单的形式基本上是:

500

当然,这整个替代方法依赖于&#34;事实上,你从来没有真正维持&#34;持久性&#34; #34;令牌&#34;在数据库中,并且在清除那些现有条目之前不会再次调用此函数。我们可以&#34; slurp&#34;在所有&#34;采取的令牌&#34;并通过相同的&#34;本地缓存&#34;排除。但随着时间的推移,这种情况会显着增长,因此在您的整体选择中需要考虑这一点。

作为最新nodejs发布的完整列表,但一般用法应用于:

let count = 0,
    seenTokens = [],
    ops = [];

// Regular loop
while ( count < 500 ) {
  const maxUse = Math.floor(Math.random() * 5) + 1;

  let token = generateToken(maxUse);

  if ( seenTokens.indexOf(token.token) === -1 ) {
    seenTokens.push(token.token);
    ops.push(token);
    count++;
  }
}
// Insert all at once
Token.insertMany(ops,(err,result) => {
  if (err) throw err;
  // now it's complete
})

或作为&#34;候补&#34;过程:

const asyncWhilst = require('async').whilst,
      mongoose = require('mongoose'),
      Schema = mongoose.Schema;

mongoose.Promise = global.Promise;
mongoose.set('debug', true);

const uri = 'mongodb://localhost/test',
      options = { useMongoClient: true };

const tokenSchema = new Schema({
  token: { type: Number, unique: true },
  maxUse: Number
});

const Token = mongoose.model('Token', tokenSchema);

// Logger helper

function log(data) {
  console.log(JSON.stringify(data,undefined,2))
}


// Function implementation
function generateToken(maxUse) {
  const min = 100000000000;
  const max = 999999999999;
  const token =  Math.floor(Math.random() * (max -min) + min);

  return Token.count({ token }).then( count => {

    if ( count > 0 ) {
      generateToken(maxUse);
    } else {
      return Token.create({ token, maxUse });
    }

  })
}

// Main program
(async function() {

  try {
    const conn = await mongoose.connect(uri,options);

    console.log("using async/await");
    // clean data
    await Promise.all(
      Object.keys(conn.models).map(m => conn.models[m].remove({}))
    );

    let count = 0;
    while (count < 500) {
      // Random usage 1-5
      const maxUse = Math.floor(Math.random() * 5) + 1;
      let token = await generateToken(maxUse);
      log(token.token);
      count++;
    }

    let totalCount = await Token.count();
    console.log("Count is: %s", totalCount);

    // Or using async.whilst
    console.log("Using async.whilst");
    // clean data
    await Promise.all(
      Object.keys(conn.models).map(m => conn.models[m].remove({}))
    );

    count = 0;
    await new Promise((resolve,reject) => {
      asyncWhilst(
        () => count < 500,
        (callback) => {
          const maxUse = Math.floor(Math.random() * 5 ) + 1;
          generateToken(maxUse).then(token => {
            log(token.token);
            count++;
            callback();
          }).catch(err => callback(err));
        },
        (err) => {
          if (err) reject(err);
          resolve();
        }
      );
    });

    totalCount = await Token.count();
    console.log("Count is: %s", totalCount);

  } catch (e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }

})();