在嵌套 forEach 中的 Promise.all 之前评估 Promise,导致 Promise.all 为空

Posted

技术标签:

【中文标题】在嵌套 forEach 中的 Promise.all 之前评估 Promise,导致 Promise.all 为空【英文标题】:Promises being evaluated before Promise.all in nested forEach, resulting in empty Promise.all 【发布时间】:2020-02-08 08:09:21 【问题描述】:

我遇到了一些不寻常的行为。

基本上,作为我的代码的一部分,我有一个函数,它利用嵌套的 for 循环来构建一个 Promise 并将其添加到 Promise 列表中。

嵌套循环完成后,我想使用 promise.all() 评估承诺列表。

过去我已经成功地使用单个 forEach 循环来做到这一点,嵌套似乎会导致一些问题,即测试显示 Promise.all 在嵌套的 forEach 循环终止之前被调用,导致它被调用一个空列表,因此返回一个空列表。

我感觉问题在于我在嵌套的 forEach 循环中的某处缺少 return 语句,如 this answer 中所述,但我无法确定在哪里。

罪魁祸首.js
const otherModule = require("blablabla")
const otherOtherModule = require("blablabla2")

function nestedFunction(list)
  var promises = [];
  list.forEach(element => 
      otherModule.getSublist(element).then(sublist => 
          sublist.forEach(subelement => 
              promises.push(otherOtherModule.promiseResolvingFunction(subelement));
          );
      );
  );
  return Promise.all(promises);


module.exports = 
  nestedFunction : nestedFunction  

罪魁祸首.test.js
const culprit = require("culpritpath")
// for mocking
const otherModule = require("blablabla")
otherModule.getSublist = jest.fn(() => Promise.resolve([, , ]))
const otherOtherModule = require("blablabla2")
otherOtherModule.promiseResolvingFunction = jest.fn(() => Promise.resolve())

describe("nestedFunction()", ()=>
  it("returns an array of resolved promises", () => 
      return culprit.nestedFunction([, ]).then(res => 
          expect(res).toHaveLength(6);
      )
  )
)

相反,我知道res[]。进一步的测试表明 promiseResolvingFunction 被调用了正确的次数,据我所知,Promise.all 在嵌套的 forEach 循环完成之前被调用。


PS:我仍然开始使用 Promise 和 TDD,我很高兴听到有关任何代码异味的反馈。

【问题讨论】:

问题是您的 .push() 调用嵌套在另一个承诺解决方案中。所以你的外部.forEach()在任何.then()s执行之前完成,当你将它传递给Promise.all()时你的promises数组仍然是空的 没错,Promise.all(promises);.then 之外被调用。所以在.then 甚至执行之前你已经在调用Promise.all 大家好,谢谢,我选择了一个解决方案。请问为什么所有解决方案都选择map而不是forEach 【参考方案1】:

是的,所以我看到的问题是您的 for each 循环正在调用异步代码并期望它同步执行。

我可能会做类似...

var promises = list.map(element => 
    return otherModule.getSublist(element).then(sublist => 

        // Map all of the sublists into a promise
        return Promise.all(sublist.map(subelement => 
            return otherOtherModule.promiseResolvingFunction(subelement));
        ));
    );
);
return Promise.all(promises);

当然,你最终会得到一个数组数组。如果您想将结果保留为子列表项的平面数组,另一种选择是首先获取所有列表,然后从这些结果中获取所有子列表...

return Promise.all(list.map( element => otherModule.getSublist(element)))
  .then((sublists) => 
    let subListPromises = [];

    // Loop through each sublist, turn each item in it into a promise
    sublists.forEach( sublist => 
        sublistPromises = [
          ...sublistPromises, 
          sublist.map( subelement => otherOtherModule.promiseResolvingFunction(subelement))
        ]
    )

    // Return a promise dependent on *all* of the sublist elements
    return Promise.all(sublistPromises)
  )

【讨论】:

非常感谢,这成功了!也感谢@trincot 对扁平化的评论。【参考方案2】:

您在数组被填充之前执行Promise.all(这是异步发生的)。

处理嵌套的 Promise 可能看起来很困难,但只需将 Promise.all 应用到 Promise 的内部数组,然后在外层,将 Promise.all 应用到所有内层的数组。

那么你还没有准备好,因为现在你有一个可以解析为数组数组的 Promise(对应于最初嵌套的 Promise),所以你需要使用全新的 .flat 方法或使用[].concat:

function nestedFunction(list) 
    // Get promise for the array of arrays of sub values
    return Promise.all(list.map(element => 
        return otherModule.getSublist(element).then(sublist => 
            // Get promise for the array of sub values
            return Promise.all(sublist.map(subelement => 
                return otherOtherModule.promiseResolvingFunction(subelement);
            ));
        );
    )).then(matrix => [].concat(...matrix)); // flatten the 2D array

【讨论】:

【参考方案3】:

您需要嵌套您的承诺解决方案。像这样的:

const otherModule = require("blablabla")
const otherOtherModule = require("blablabla2")

function nestedFunction(list)
  var promises =
  list.map(element => 
      return otherModule.getSublist(element).then(sublist => 
          return Promise.all(
            sublist.map(subelement => 
              return otherOtherModule.promiseResolvingFunction(subelement);
            )
         );
      );
  );
  return Promise.all(promises);


module.exports = 
  nestedFunction : nestedFunction  

【讨论】:

您好,谢谢您的回答。恐怕这是为了保证承诺是undefined。我在最后的 return 语句之前添加了一个 console.log(promises) 来检查。会不会是因为我的嘲笑过于简单化了?顺便说一句,有人来并否决了所有问题/cmets,不是我。谢谢。 这就是我在手机上编写代码所得到的。编辑在第一张地图中添加缺少的回报

以上是关于在嵌套 forEach 中的 Promise.all 之前评估 Promise,导致 Promise.all 为空的主要内容,如果未能解决你的问题,请参考以下文章

视图中的嵌套 ForEach(和列表)给出了意想不到的结果

我可以使用 Pig Latin 中的嵌套 FOREACH 语句生成嵌套包吗?

通过嵌套的 foreaches 取消设置多维数组中的键

如何取消设置嵌套 foreach 中的特定数组?

如何取消设置嵌套 foreach 中的每个数组?

如何遍历 <c:forEach> 中的嵌套映射