异步等待在地图功能中无法正常工作

Posted

技术标签:

【中文标题】异步等待在地图功能中无法正常工作【英文标题】: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 并执行您需要的任何操作。 【参考方案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);

【讨论】:

以上是关于异步等待在地图功能中无法正常工作的主要内容,如果未能解决你的问题,请参考以下文章

等待异步功能完成内部地图

使用等待和异步返回字符串列表

无法从异步函数获取返回值,等待未按预期工作(Vue API 服务)

如何使单独的同步功能等待另一个异步功能?

Promise.all 和 .map 函数的异步/等待无法按预期工作

如何等待此功能完成?