在我看来,您最好保留生成令牌的“本地列表”并通过.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
}
);
所以一切都比较简单。
保持唯一的本地和“批量插入”
处理此问题的“替代”方法是将生成的令牌数组保留在“客户端”上。然后,您在每次随机生成时所需要做的就是查看令牌是否“已经看到”,并且仅在获得“唯一”值时才创建插入操作。
这应该比通过递归调用来回访问数据库要快得多,因为它都是在本地“缓存”的。
本质上,让你的生成器功能非常基本:
function generateToken(maxUse) {
const min = 100000000000;
const max = 999999999999;
const token = Math.floor(Math.random() * (max -min) + min);
return ({ token, maxUse });
}
然后在循环期间,为seenTokens 和ops 创建两个数组,其中后者表示稍后以“批量”而不是单独写入的方式插入的项目:
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() 方法是异步的,如果您实际上没有插入“数万”,那么它应该很容易处理,甚至不需要“ await" 这样的调用,然后只发出 "once"。
但是这里的演示说明了当代码“数以万计”没有其他更改时应该是什么样子。同样,您可以根据需要使用其他库函数来“等待”此类调用。
我们可以再次使用async.series 和async.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 条目并只调用@ 987654326@,就像这里的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
})
当然,这整个替代方法“取决于”这样一个事实,即您从未真正维护数据库中“令牌”的“持久性”,并且在清除那些现有条目之前不会再次调用此函数。我们可以在所有“获取的令牌”中“啜饮”并通过相同的“本地缓存”排除。但随着时间的推移,这将显着增长,因此在您的整体选择中需要考虑这一点。
作为最新 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();
}
})();