异步映射功能中的计数器未增加

生产

我正在使用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]);

问题是计数器并非每次都在增加。我在所有已创建的客户中得到相同的计数器。这是什么问题?

问题是这样的,但没有答案。

TJ人群

问题是所有呼叫都重叠。由于他们各自要做的第一件事是获得当前计数器,因此他们都获得相同的计数器,然后尝试使用它。从根本上讲,您不想这样做:

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] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章