我正在使用mongodb和nodejs。我必须创建一系列内部数据库的客户。
const promises2 = customers.map(async customer => {
if (!customer.customerId) {
const counter = await Counter.findOne({ type: "Customer" });
console.log({counter});
const payload = {
customerId: counter.sequence_value,
};
await Customer.create(payload);
await Counter.findOneAndUpdate({ type: "Customer" }, { $inc: { sequence_value: 1 } });
}
});
await Promise.all([...promises2]);
问题是计数器并非每次都在增加。我在所有已创建的客户中得到相同的计数器。这是什么问题?
问题是这样的,但没有答案。
问题是所有呼叫都重叠。由于他们各自要做的第一件事是获得当前计数器,因此他们都获得相同的计数器,然后尝试使用它。从根本上讲,您不想这样做:
const counter = await Counter.findOne({ type: "Customer" });
// ...
await Counter.findOneAndUpdate({ type: "Customer" }, { $inc: { sequence_value: 1 } });
...因为它创建了竞争条件:重叠的异步操作都可以获取相同的序列值,然后都对其进行更新。
您需要一个原子操作来递增和检索新的ID。我不使用MongoDB,但我认为findOneAndUpdate
如果添加该returnNewDocument
选项,该操作可以为您完成此操作。如果是这样,最小的更改将是转换为使用:
const promises2 = customers.map(async customer => {
if (!customer.customerId) {
const counter = await Counter.findOneAndUpdate(
{ type: "Customer" },
{ $inc: { sequence_value: 1 } },
{ returnNewDocument: true }
);
console.log({counter});
const payload = {
customerId: counter.sequence_value,
};
await Customer.create(payload);
}
});
await Promise.all([...promises2]);
...但是没有理由创建一个数组然后立即复制它,直接使用它即可:
await Promise.all(customers.map(async customer => {
if (!customer.customerId) {
const counter = await Counter.findOneAndUpdate(
{ type: "Customer" },
{ $inc: { sequence_value: 1 } },
{ returnNewDocument: true }
);
console.log({counter});
const payload = {
customerId: counter.sequence_value,
};
await Customer.create(payload);
}
}));
如果有任何失败,则整个操作将失败,并且只有第一个失败会报告回给您的代码(其他操作将继续,然后视情况而定成功或失败)。如果您想知道所有发生的事情(在这种情况下可能很有用),可以使用allSettled
代替all
:
// Gets an array of {status, value/reason} objects
const results = await Promise.allSettled(customers.map(async customer => {
if (!customer.customerId) {
const counter = await Counter.findOneAndUpdate(
{ type: "Customer" },
{ $inc: { sequence_value: 1 } },
{ returnNewDocument: true }
);
console.log({counter});
const payload = {
customerId: counter.sequence_value,
};
await Customer.create(payload);
}
}));
const errors = results.filter(({status}) => status === "rejected").map(({reason}) => reason);
if (errors.length) {
// Handle/report errors here
}
Promise.allSettled
是ES2021中的新增功能,但如有需要,可轻松进行填充。
如果我由于findOneAndUpdate
某种方式对上述用法有误解,可以肯定的是MongoDB为您提供了一种在没有竞争条件的情况下获取这些ID的方法。但是在最坏的情况下,您可以改为预先分配ID,如下所示:
// Allocate IDs (in series)
const ids = [];
for (const customer of customers) {
if (!customer.customerId) {
const counter = await Counter.findOne({ type: "Customer" });
await Counter.findOneAndUpdate({ type: "Customer" }, { $inc: { sequence_value: 1 } });
ids.push(counter.sequence_value);
}
}
// Create customers (in parallel)
const results = await Promise.allSettled(customers.map(async(customer, index) => {
const customerId = ids[index];
try {
await Customer.create({
customerId
});
} catch (e) {
// Failed, remove the counter, but without allowing any error doing so to
// shadow the error we're already handling
try {
await Counter.someDeleteMethodHere(/*...customerId...*/);
} catch (e2) {
// ...perhaps report `e2` here, but don't shadow `e`
}
throw e;
}
});
// Get just the errors
const errors = results.filter(({status}) => status === "rejected").map(({reason}) => reason);
if (errors.length) {
// Handle/report errors here
}
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句