调用堆栈和承诺链 - 即“深”和“宽”。
事实上,没有。正如我们所知,这里没有承诺链doSomeThingAsynchronous.then(doSomethingAsynchronous).then(doSomethingAsynchronous).…
(这是什么Promise.each
or Promise.reduce
如果以这种方式编写,可能会顺序执行处理程序)。
What we are facing here is a resolve chain1 - what happens in the end, when the base case of the recursion is met, is something like Promise.resolve(Promise.resolve(Promise.resolve(…)))
. This is only "deep", not "wide", if you want to call it that.
我预计内存峰值会比执行递归或单独构建承诺链更大。
实际上不是尖峰。随着时间的推移,你会慢慢地建立起大量的承诺,并用最内在的承诺来解决,所有这些都代表相同的结果。当任务结束时,条件得到满足并且最里面的承诺以实际值解决,所有这些承诺都应该以相同的值解决。那最终会是O(n)
沿着解析链向上走的成本(如果简单地实现,这甚至可能会递归地完成并导致堆栈溢出)。之后,除了最外面的承诺之外的所有承诺都可以被垃圾收集。
相比之下,由类似的东西构建的承诺链
[…].reduce(function(prev, val) {
// successive execution of fn for all vals in array
return prev.then(() => fn(val));
}, Promise.resolve())
会显示一个尖峰,分配n
同时处理 Promise 对象,然后慢慢地逐一解决它们,对之前的对象进行垃圾收集,直到只有已解决的最终 Promise 还活着。
memory
^ resolve promise "then" (tail)
| chain chain recursion
| /| |\
| / | | \
| / | | \
| ___/ |___ ___| \___ ___________
|
+----------------------------------------------> time
是这样吗?
Not necessarily. As said above, all the promises in that bulk are in the end resolved with the same value2, so all we would need is to store the outermost and the innermost promise at one time. All intermediate promises may become garbage-collected as soon as possible, and we want to run this recursion in constant space and time.
事实上,这种递归结构对于异步循环 https://stackoverflow.com/a/24660323/1048572在动态条件下(没有固定的步骤数),你无法真正避免它。在 Haskell 中,它一直被用于IO
monad,正是因为这种情况才对其进行了优化。它非常类似于尾调用递归 https://en.wikipedia.org/wiki/Tail_call,它通常会被编译器消除。
有没有人考虑过以这种方式构建链的内存问题?
是的。这是在 Promise/aplus 讨论过 https://github.com/promises-aplus/promises-spec/issues/179例如,虽然还没有结果。
许多 Promise 库确实支持迭代助手以避免 Promise 峰值then
链条,如蓝鸟的each
and map
方法。
My own promise library3,4 does feature resolve chains without introducing memory or runtime overhead. When one promise adopts another (even if still pending), they become indistinguishable, and intermediate promises are no longer referenced anywhere.
Promise 库之间的内存消耗会有所不同吗?
是的。虽然这种情况可以优化,但这种情况很少发生。具体来说,ES6 规范确实要求 Promises 在每次检查时检查该值resolve
调用,因此不可能崩溃链。链中的承诺甚至可以用不同的值来解决(通过构建一个滥用吸气剂的示例对象,而不是在现实生活中)。问题在 esdiscuss 上提出 https://mail.mozilla.org/pipermail/es-discuss/2015-April/042517.html但仍未解决。
因此,如果您使用泄漏实现,但需要异步递归,那么您最好切换回回调并使用延迟反模式 https://stackoverflow.com/q/23803743/1048572将最里面的承诺结果传播到单个结果承诺。
[1]: no official terminology
[2]: well, they are resolved with each other. But we want to resolve them with the same value, we expect that
[3]: undocumented playground, passes aplus. Read the code at your own peril: https://github.com/bergus/F-Promise https://github.com/bergus/F-Promise
[4]: also implemented for Creed in this pull request https://github.com/briancavalier/creed/pull/9