在嵌套 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 中所述,但我无法确定在哪里。
罪魁祸首.jsconst 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 为空的主要内容,如果未能解决你的问题,请参考以下文章