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

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

我正在与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 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
调度的第一个计时器回调在数组上推送一个值,然后其他四个看到数组中有一个值并拒绝。