【问题标题】:Promises being evaluated before Promise.all in nested forEach, resulting in empty Promise.all在嵌套 forEach 中的 Promise.all 之前评估 Promise,导致 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

标签: javascript node.js asynchronous promise jestjs


【解决方案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,不是我。谢谢。
    • 这就是我在手机上编写代码所得到的。编辑在第一张地图中添加缺少的回报
    猜你喜欢
    • 1970-01-01
    • 2016-11-20
    • 2018-12-02
    • 1970-01-01
    • 2018-08-29
    • 1970-01-01
    • 2018-05-11
    • 2019-09-28
    相关资源
    最近更新 更多