javascript - 使用异步 while 循环创建和插入文档

标签 javascript node.js mongodb mongoose promise

我正在尝试动态生成 token 并将它们保存到数据库中。

这是生成 token 的代码。

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)。

欢迎使用替代方法

谢谢。

最佳答案

在我看来,您最好保留生成的 token 的“本地列表”并通过 .insertMany() 进行“批量”插入.里程可能因实际实现而异,因此我们将讨论该方法以及使用异步方法以理智的方式处理递归函数。

异步循环

您创建了一个问题,您需要测试存在的值以确定它们对于插入是“唯一的”。这当然需要异步调用才能查看数据库,因此排除了诸如“更新插入”之类的“批量”操作,因为在循环发送该项目之前您不知道该项目是否存在。所以递归在这种情况下确实有效。

因此,您应该做的第一件事就是使“函数”本身异步,返回回调或 promise 。

本质上:

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.whilst如果在不支持 async/await 的 Node 版本下运行:

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

所以这一切都相对简单。


保留唯一本地和“批量插入”

处理此问题的“替代”方法是将生成的 token 数组“保存在客户端”。然后,您需要在每个随机生成上做的就是查看 token 是否“已经看到”,并且只有在获得“唯一”值时才创建插入操作。

这应该比使用递归调用来回访问数据库要快得多,因为它都在本地“缓存”。

本质上,让你的生成器函数非常基础:

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

  return ({ token, maxUse });
}

然后在循环期间,为 seenTokensops 创建两个数组,其中后者表示稍后要插入“批量”而不是单个的项目写道:

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()。方法是异步的,如果您实际上没有插入“数万”,那么它应该很容易处理,甚至不需要“等待”这样的调用,然后只发出“一次”

但是这里的演示说明了代码在“数以万计”且没有其他更改的情况下应该是什么样子。同样,您可以根据需要使用其他库函数来“等待”此类调用。

我们又可以使用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
    }
  );

都非常相似,而且“流量控制”实际上只是为了满足“更大的批处理”,您可以简单地使用常规循环来构建 ops 条目并使一个电话只给.insertMany() ,就像这里的 500 限制一样。

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

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

当然,这整个替代方法“取决于”这样一个事实,即您实际上从未在数据库中维护“ token ”的“持久性”,并且在清除这些现有条目之前不会再次调用此函数。我们可以“吞噬”所有“获取的 token ”并通过相同的“本地缓存”排除。但随着时间的推移,这会显着增长,因此在您的整体选择中需要考虑这一点。


作为最新 nodejs 版本的脚手架的完整列表,但内部应用了一般用法:

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

})();

或作为“替代”过程:

const asyncSeries = require('async').series,
      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, 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,
        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 = [];
    }

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

    // using async.whilst and indeed async.series for control
    console.log("using asyc.whilst");
    await Promise.all(
      Object.keys(conn.models).map(m => conn.models[m].remove({}))
    );

    await new Promise((resolve,reject) => {
      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) reject(err);
          ops = [];
          resolve();
        }
      );

    });

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


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

})();

关于javascript - 使用异步 while 循环创建和插入文档,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45203218/

相关文章:

node.js - ForEach 不适用于 mongodb,但在本地数组声明中工作正常

mongodb - 如何在 MongoDB 上创建包含数组的数组

javascript - 匹配 Mongoose 中带或不带 http(s) 或 www 的任何 url

javascript - html <div> 似乎没有被识别

javascript div 关闭问题

javascript - 数据关闭时 npm run watch 无法编译

javascript - 回调不返回

javascript - Webpack 内联 CSS 背景图像

javascript - 如何使用 Sinon 来 stub 发出 REST 请求的调用

javascript - 编译或混淆 Node js