Javascript 异步映射函数中的计数器未增加
我正在与mongodb和nodejs合作。我有一个客户阵列,我必须在数据库内部创建每个客户Javascript 异步映射函数中的计数器未增加,javascript,node.js,mongodb,asynchronous,async-await,Javascript,Node.js,Mongodb,Asynchronous,Async Await,我正在与mongodb和nodejs合作。我有一个客户阵列,我必须在数据库内部创建每个客户 const promises2 = customers.map(async customer => { if (!customer.customerId) { const counter = await Counter.findOne({ type: "Customer" }); console.log({counter}); const
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 promises2 = [];
customers.map((customer) => {
return new Promise(async (resolve) => {
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 } });
}
resolve();
});
});
await Promise.all(promises2);
问题是所有调用都重叠。因为他们每个人做的第一件事就是得到当前计数器,所以他们都得到相同的计数器,然后尝试使用它。基本上,您不想这样做:
const counter = await Counter.findOne({ type: "Customer" });
// ...
await Counter.findOneAndUpdate({ type: "Customer" }, { $inc: { sequence_value: 1 } });
…因为它创建了竞争条件:重叠的异步操作都可以获得相同的序列值,然后都会对其进行更新
您需要一个用于递增和检索新ID的原子操作。我不使用MongoDB,但我认为如果您添加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
}
在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
}
请核对问题。我已使我的
map
函数异步。因此,无需再次返回新承诺
。事情就是这样。它正在同时解决承诺问题。因此,它首先找到所有计数器,然后立即更新所有计数器。这太荒谬了。仅供参考,我刚刚纠正了我答案中一个非常严重的错误部分,在“总体操作…”开头的一段中:-)快乐编码!天啊!!!太神了因为这个问题,我差点哭了,本来打算用来做循环
,但这一次几乎消除了我所有的疑虑。感谢您的美丽,感谢您阅读mongodb。Hi T.J.Promise。如果其中任何操作失败,所有操作都将失败。我的意思是,假设我必须插入5个文档,在插入第3个文档时出现一些错误,然后插入前2个文档,而第4个和第5个文档没有插入。所以我要做的是,我也要做第四和第五,也就是说,那些没有任何错误的。任何建议。在这里您可以看到@Profer-您可能需要新的(ish),它等待所有的承诺都解决,然后提供一系列结果。结果是具有状态
属性的对象,该属性要么是“已满足”
,要么是“已拒绝”
。履行承诺的对象具有一个值
属性和结果;拒绝承诺的对象有一个带有拒绝原因的reason
属性。:-)谢谢你的回复,当array.length==1
时,你应该给我一次被拒绝的时间,但它给出的时间是原来的四倍。为什么?@Profer-因为array.length==1
是真的四次,而不是一次。由f2
调度的第一个计时器回调在数组上推送一个值,然后其他四个看到数组中有一个值并拒绝。