一个调用栈和一个Promise链——即“深”和“宽”。
实际上,没有。正如我们所知道的那样,这里没有Promise链doSomeThingAsynchronous.then(doSomethingAsynchronous).then(doSomethingAsynchronous).…
(如果以这种方式编写,这就是Promise.each
或Promise.reduce
可能会顺序执行处理程序)。
我们在这里面临的是一个解析链1 - 最终会发生什么,当满足递归的基本情况时,类似于Promise.resolve(Promise.resolve(Promise.resolve(…)))
. 如果你想这样称呼它,这只是“深”,而不是“宽”。
我预计内存峰值会比单独执行递归或构建Promise链要大。
实际上不是尖峰。随着时间的推移,你会慢慢地构建大量的Promise,并用最里面的Promise解决,所有Promise都代表相同的结果。当在你的任务结束时,条件得到满足并且最内层的Promise用实际值解决时,所有这些Promise都应该用相同的值解决。这最终会O(n)
导致遍历解析链的成本(如果天真地实现,这甚至可能以递归方式完成并导致堆栈溢出)。之后,除了最外层的所有Promise都可以成为垃圾收集。
相比之下,由类似的东西构建的Promise链
[…].reduce(function(prev, val) {
// successive execution of fn for all vals in array
return prev.then(() => fn(val));
}, Promise.resolve())
会显示一个尖峰,同时分配n
promise 对象,然后一个一个慢慢地解决它们,垃圾收集之前的那些,直到只有已解决的 end promise 还活着。
memory
^ resolve promise "then" (tail)
| chain chain recursion
| /| |\
| / | | \
| / | | \
| ___/ |___ ___| \___ ___________
|
+----------------------------------------------> time
是这样吗?
不必要。如上所述,该批量中的所有Promise最终都以相同的值2解析,因此我们需要的只是一次存储最外层和最内层的Promise。所有中间Promise可能会尽快被垃圾收集,我们希望在恒定的空间和时间中运行这个递归。
事实上,这种递归结构对于具有动态条件(没有固定步数)的异步循环是完全必要的,你无法真正避免它。在 Haskell 中,它一直用于IO
monad,因此对其进行了优化。它与尾调用递归非常相似,后者通常被编译器消除。
有没有人考虑过这样建链的内存问题?
是的。例如,这已在 promises/aplus 上讨论过,但还没有结果。
许多Promise库确实支持迭代助手来避免Promisethen
链的峰值,例如 Bluebirdeach
和map
方法。
我自己的Promise库3,4没有引入内存或运行时开销的特性解析链。当一个Promise采用另一个Promise时(即使仍然未决),它们变得不可区分,并且中间Promise不再在任何地方被引用。
Promise库之间的内存消耗会有所不同吗?
是的。虽然这种情况可以优化,但很少。具体来说,ES6 规范确实要求 Promise 在每次resolve
调用时检查值,因此不可能折叠链。链中的Promise甚至可以用不同的值来解决(通过构造一个滥用 getter 的示例对象,而不是在现实生活中)。该问题已在 esdiscuss 上提出,但仍未解决。
因此,如果您使用泄漏实现,但需要异步递归,那么您最好切换回回调并使用延迟反模式将最内层的Promise结果传播到单个结果Promise。
[1]:没有官方术语
[2]:好吧,它们是相互解决的。但是,我们希望用相同的值来解决这些问题,我们期待的是
[3]:无证操场,通过Aplus的。阅读代码请自担风险:https : //github.com/bergus/F-Promise
[4]:也在此拉取请求中为 Creed 实现