【问题标题】:Async await not work properly inside map function异步等待在地图功能中无法正常工作
【发布时间】:2020-02-18 15:30:47
【问题描述】:

我有一个这样的数据集结构:

  _id: SomeMongoID,
  value: "a",
  counter: 1
}

所以最初我的数据库表是空的。 现在我有一个数组,其中值如下: const array = ["a", "a", "a"]

我最初想要的是我第一次进行搜索,所以它会清空结果,所以在这种情况下插入查询,现在下次它获取条目时,只需增加计数器。

为此,我编写了代码:

const testFunction = async(array) => {
  try {
    await Promise.all(
      array.map(async x => {
       const data = await CollectionName.findOne({value: x}).exec();
       // Every time data will return null
       if (data) {
         //In this case only counter will have to increase
         // But this block not run 
       } else {
         //So by this first value will store
         const value = new Value({
          value: x,
          counter: 1
         });
         await value.save()
       }
      })
    )
  } catch (error) {
    console.log(error)
  }
}
const array = ["a", "a", "a"]
testFunction(array);

问题是它会创建 3 个条目而不是单个条目。 map 函数不会等待,我使用 console.log() 通过手动调试检查。非常感谢任何帮助或建议。

【问题讨论】:

  • 为什么要等待?你不是 awaiting 从你传递给 map 的函数返回的承诺,直到它们都开始并被包裹在 Promise.all
  • @Quentin 我更新了这个问题。这将创建 3 个条目而不是单个条目。
  • 您可以执行const dataArray = await Promise.all(array.map(x => CollectionName.findOne({value: x})),然后遍历该dataArray 并执行您需要的任何操作。

标签: javascript node.js ecmascript-6 promise async-await


【解决方案1】:

您不必在这里使用map。您可以使用 for ... of 迭代来等待结果。

const testFunction = async(array) => {
  try {
    for (const x of array) {
      const data = await CollectionName.findOne({value: x}).exec();
      // Every time data will return null
      if (data) {
        //In this case only counter will have to increase
        // But this block not run 
      } else {
        //So by this first value will store
        const value = new Value({
        value: x,
        counter: 1
        });
        await value.save()
      }
    }
  } catch (error) {
    console.log(error)
  }
}
const array = ["a", "a", "a"]
testFunction(array);

解释:map 不会等待,即使传递函数是 async。相反,它收集您传递给它的函数的返回值。 async 函数总是返回 Promise。然后Promise.all 等待所有人。但是对数据的迭代不会等待任何东西。它将在评估第一个 await 关键字的右侧时立即返回承诺。但是for ... of 有效,因为它不使用回调函数。相反,它评估每个循环并正确等待您希望它这样做的地方。然后执行非常接近,好像里面的所有代码都是同步的。

【讨论】:

  • 在循环中使用 await 是一种反模式,本质上是阻塞行为。应将一组 Promise 传递给 Promise.all 以利用其并行能力。
  • @DanStarns 你是对的。但是不知道这个人到底想达到什么目的,'sync' for...of 循环是这个地方最安全的解决方案。但是,嘿,我将使用并行性添加另一个答案。
【解决方案2】:

为了节省时间和并行加载数据,首先处理我们自己拥有的值。我们创建了一个数据结构,它甚至在对数据库进行单次调用之前就已经计算了相同的值。然后我们只为我们的数据结构中的唯一键调用数据库。这将您示例中的调用次数从 3 减少到 1。在我的示例中,我向测试数据添加了两个 "b" 值。所以调用次数将是 2 而不是 5。

然后在数据库中查询唯一键。如果找到一个条目,则计数器会增加测试数组中出现的value 的数量。如果未找到,则会创建一个新条目,并将计数器设置为找到的出现次数。

const testFunction = async (array) => {
  try {

    // Create a statistics by counting each value fist
    // Creates a structure like { a: 3, b: 2}
    const countsByValues = array.reduce((acc, value) => {
      const newCount = acc[value] ? acc[value] + 1 : 1;
      return {
        ...acc,
        value: newCount
      };
    }, {});

    await Promise.all(
      // Use object entries to get array of [["a", 3], ["b", 2]]
      Object.entries(countsByValues).map(async ([x, count]) => {
       const data = await CollectionName.findOne({value: x}).exec();
       if (data) {
         //In this case only counter will have to increase by the count
         data.counter += count;
         //save data - insert your code saving code here
         await data.save();
       } else {
         //So by this first value will store
         const value = new Value({
          value: x,
          // new values will be the total count
          counter: count
         });
         await value.save()
       }
      })
    )
  } catch (error) {
    console.log(error)
  }
}
const array = ["a", "a", "b", "b", "a"]
testFunction(array);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-16
    相关资源
    最近更新 更多