【问题标题】:Node.js/ - MongoDB async/await weird behaviour?Node.js/ - MongoDB 异步/等待奇怪的行为?
【发布时间】:2021-11-14 09:34:42
【问题描述】:

这里,productIds 具有数组中所有已订购商品的 ID,例如 ["1294382", "2913892"]

所以我试图从 MongoDB 的数据库中获取所有具有这些 ID 的项目。我正在映射它们中的每一个,并将它们存储在orderedItems 数组中。这样我就可以访问数据库中项目的价格(或任何属性)。

productRouter.post("/", async (req, res) => {
 
  const productIds = req.body.orderedItems.map((item) => item.id); 

  let orderedItems = [];

  productIds.map(async (productId) => {
    const d = await Product.findById(productId);
    orderedItems.push(d);
  });

  const unusedVariable = await Product.findById("61...da1"); //If i delete that line (or only await), orderedItems returns empty array. 

  console.log(orderedItems);
  
});

现在,预期的输出是:

[
  {
    _id: new ObjectId("614632cc8aa9513567dfbca4"),
    name: 'ürün1',
    desc: 'ürün1 açıklama',
    price: 50,
    __v: 0
  },
  {
    _id: new ObjectId("614632cc8aa9513567dfbca2"),
    name: 'kartal kupa',
    desc: 'kartal resimli kupa',
    price: 50,
    __v: 0
  }
]

我得到了那个输出,但前提是我有等待的未使用变量行。如果我删除该行,或从那里删除“等待”,我得到的输出是: [] 。所以一个空数组而不是用对象填充。

为什么?我该如何解决这个问题(把 unusuedVariable 留在那里?)?这段代码有多糟糕?

【问题讨论】:

  • 1.当你的意思是forEach 时,不要使用map(这很令人困惑) 2. 回调中带有 await 的 forEach 循环不会按顺序运行,并且代码不会等待承诺完成。使用const orderedItems = await Promise.all(productIds.map(productId => Product.findById(productId)));
  • 您应该尝试创建一个 findById 操作数组并将该数组传递给 Promise.all() 并等待。
  • 你正在推送一个回调,确保你将回调设为异步,但没有等待回调

标签: javascript node.js mongodb async-await


【解决方案1】:

您遇到了并发问题。​没有什么不寻常的,你不是在等待(1) 的承诺结束。您的代码正常工作的唯一原因是您的情况下的 MongoDB 使用单个连接,因此 (2) 仅在 (1) (所有这些)完成后才排队和完成(这是您的幸运巧合)。

productRouter.post("/", async (req, res) => {
 ​const productIds = req.body.orderedItems.map((item) => item.id);
 ​let orderedItems = [];

 ​productIds.map(async (productId) => { // (1)
   ​const d = await Product.findById(productId);
   ​orderedItems.push(d);
 ​});

 ​const unusedVariable = await Product.findById("61...da1"); // (2)
 ​console.log(orderedItems);
});

如前所述,map 和 forEach 均不支持承诺(由于向后兼容性,它们可能永远不会支持)因此您应该使用Promise.all,这可能会给您一些同意,或者使用普通的@987654330 @ 与 async/await 语法兼容。

productRouter.post("/", async (req, res) => {
  const productIds = req.body.orderedItems.map((item) => item.id);
  let orderedItems = [];

  for (const productId: productIds) {
    orderedItems.push(await Product.findById(productId));
  }
  
  console.log(orderedItems);
});

或者

productRouter.post("/", async (req, res) => {
  const productIds = req.body.orderedItems.map((item) => item.id);
  
  const orderedItems = await Promise.all(
    productIds.map(productId => Product.findById(productId))
  );
  
  console.log(orderedItems);
});

题外话

如 cmets 中所述,您应该考虑使用 $in 运算符,这将大大减少针对您的数据库的查询数量:

productRouter.post("/", async (req, res) => {
  const productIds = req.body.orderedItems.map((item) => item.id);
  const orderedItems = Product.find({_id: {$in: productIds}});
  console.log(orderedItems);
});

More here

【讨论】:

  • 谢谢。这回答了一切。因此,当我在回调函数中使用 await 时,代码不会等待它完成任务。因此返回空的orderedItems。所以我只是在这种情况下使用 Promise.all 。您提供的代码也有效。
  • 不完全是,当您使用await 时,您等待承诺结束。但是当您使用 async 时,您只需声明一个返回 Promise 的函数。 (在你的情况下,productIds.map(async ... 会返回一系列承诺,但没有人在等待它们)
  • 上面提到的链接会更好的解释这个过程:stackoverflow.com/questions/14220321/…
  • 好吧,我会研究的。同样关于 Offtopic 部分,这可能对某人有所帮助:您提供的代码无法按预期工作,但这确实(需要将它们变成 ObjectId):const objIds = productIds.map((id) => mongoose.Types.ObjectId(id)); const orderedItems = await Product.find({ _id: { $in: objIds }, }); 我会使用那个,因为它更优化跨度>
猜你喜欢
  • 1970-01-01
  • 2017-09-13
  • 2018-07-18
  • 1970-01-01
  • 2023-02-21
  • 2022-01-08
  • 1970-01-01
  • 1970-01-01
  • 2018-08-14
相关资源
最近更新 更多