异步映射函数中的计数器不增加



我正在使用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,但我认为如果您添加returnNewDocument选项,findOneAndUpdate操作可以为您做到这一点。如果是这样,最小的更改是换成使用它:

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
}

您的映射函数没有返回承诺。

试试这个:

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

最新更新