在这个答案中,一个promise链是递归构建的。
略有简化,我们有:
function foo() { function doo() { // always return a promise if (/* more to do */) { return doSomethingAsync().then(doo); } else { return Promise.resolve(); } } return doo(); // returns a promise }
据推测,这将产生一个调用栈 和 一个promise链,即“深”和“宽”。
我预计内存峰值将比执行递归或单独建立承诺链更大。
调用堆栈和承诺链-即“深”和“宽”。
其实没有 据我们所知,这里没有promise链doSomeThingAsynchronous.then(doSomethingAsynchronous).then(doSomethingAsynchronous).…(如果以这种方式编写,则按顺序执行处理程序是Promise.each或Promise.reduce可能做的事情)。
doSomeThingAsynchronous.then(doSomethingAsynchronous).then(doSomethingAsynchronous).…
Promise.each
Promise.reduce
我们在这里面对的是一个 _解决链_1-当满足递归的基本情况时,最终会发生类似的事情Promise.resolve(Promise.resolve(Promise.resolve(…)))。如果您要称呼它,那只是“深”,而不是“宽”。
Promise.resolve(Promise.resolve(Promise.resolve(…)))
实际上不是峰值。随着时间的流逝,您会慢慢建立大量的承诺,并用最内层的承诺来解决,所有承诺都代表相同的结果。在任务结束时,当条件满足且最内层的承诺以实际值解决时,所有这些承诺都应以相同的值解决。这最终将O(n)花费沿解析链向上移动的成本(如果天真地实现,甚至可能以递归方式进行,并导致堆栈溢出)。之后,除最外层的所有承诺都将变为垃圾回收。
O(n)
相比之下,由类似
[…].reduce(function(prev, val) { // successive execution of fn for all vals in array return prev.then(() => fn(val)); }, Promise.resolve())
会显示峰值,同时分配npromise对象,然后慢慢地一个一个地解决它们,垃圾回收先前的对象,直到只有已解决的最终promise仍然存在。
n
memory ^ resolve promise "then" (tail) | chain chain recursion | /| |\ | / | | \ | / | | \ | ___/ |___ ___| \___ ___________ | +----------------------------------------------> time
是这样吗
不必要。如上所述,该批量中的所有promise最终都使用相同的值2进行解析,因此我们需要的是一次存储最外部和最内部的promise。所有中间的Promise可能会尽快变为垃圾回收,我们希望在恒定的空间和时间中运行此递归。
实际上,对于具有动态条件(无固定步数)的异步循环,此递归构造是完全必要的,您无法避免。在Haskell中,IOmonad 一直使用该代码,仅由于这种情况而对其进行了优化。它与尾调用递归非常相似,后者通常被编译器消除。
IO
有没有人考虑过以这种方式构建链的内存问题?
是。这是在承诺/ Aplus的讨论,例如,虽然没有结局呢。
许多承诺库确实支持迭代助手,以避免then诸如Bluebird each和map方法之类的承诺链尖峰。
then
each
map
我自己的诺言库3,4确实具有解析链,而没有引入内存或运行时开销。当一个承诺采纳另一个承诺(即使仍未完成)时,它们就变得难以区分,中间的承诺不再在任何地方被引用。
承诺库之间的内存消耗会有所不同吗?
是。尽管这种情况可以优化,但很少如此。具体来说,ES6规范确实要求Promises在每次resolve通话时检查该值,因此无法折叠链。甚至可以用不同的值来解决链中的承诺(通过构造一个滥用吸气剂的示例对象,而不是现实生活中的对象)。该问题是在esdiscuss上提出的,但仍未解决。
resolve
因此,如果使用泄漏的实现,但需要异步递归,则最好切换回回调并使用延迟的反模式将最内层的Promise结果传播到单个结果Promise。
[1]:没有官方术语 [2]:嗯,它们是相互解决的。但是,我们 希望 用相同的值来解决这些问题,我们 期待 的是 [3]:无证操场,通过Aplus的。阅读代码的后果自负 : [4]:在此pull请求中也为Creed实现