小编典典

在JavaScript中以递归方式构建承诺链-内存注意事项

javascript

在这个答案中,一个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链,即“深”和“宽”。

我预计内存峰值将比执行递归或单独建立承诺链更大。

  • 是这样吗
  • 有没有人考虑过以这种方式构建链的内存问题?
  • 承诺库之间的内存消耗会有所不同吗?

阅读 290

收藏
2020-04-25

共1个答案

小编典典

调用堆栈和承诺链-即“深”和“宽”。

其实没有
据我们所知,这里没有promise链doSomeThingAsynchronous.then(doSomethingAsynchronous).then(doSomethingAsynchronous).…(如果以这种方式编写,则按顺序执行处理程序是Promise.eachPromise.reduce可能做的事情)。

我们在这里面对的是一个 _解决链_1-当满足递归的基本情况时,最终会发生类似的事情Promise.resolve(Promise.resolve(Promise.resolve(…)))。如果您要称呼它,那只是“深”,而不是“宽”。

我预计内存峰值将比执行递归或单独建立承诺链更大。

实际上不是峰值。随着时间的流逝,您会慢慢建立大量的承诺,并用最内层的承诺来解决,所有承诺都代表相同的结果。在任务结束时,当条件满足且最内层的承诺以实际值解决时,所有这些承诺都应以相同的值解决。这最终将O(n)花费沿解析链向上移动的成本(如果天真地实现,甚至可能以递归方式进行,并导致堆栈溢出)。之后,除最外层的所有承诺都将变为垃圾回收。

相比之下,由类似

[…].reduce(function(prev, val) {
    // successive execution of fn for all vals in array
    return prev.then(() => fn(val));
}, Promise.resolve())

会显示峰值,同时分配npromise对象,然后慢慢地一个一个地解决它们,垃圾回收先前的对象,直到只有已解决的最终promise仍然存在。

memory
  ^     resolve      promise "then"    (tail)
  |      chain          chain         recursion
  |        /|           |\
  |       / |           | \
  |      /  |           |  \
  |  ___/   |___     ___|   \___     ___________
  |
  +----------------------------------------------> time

是这样吗

不必要。如上所述,该批量中的所有promise最终都使用相同的值2进行解析,因此我们需要的是一次存储最外部和最内部的promise。所有中间的Promise可能会尽快变为垃圾回收,我们希望在恒定的空间和时间中运行此递归。

实际上,对于具有动态条件(无固定步数)的异步循环,此递归构造是完全必要的,您无法避免。在Haskell中,IOmonad
一直使用该代码,仅由于这种情况而对其进行了优化。它与尾调用递归非常相似,后者通常被编译器消除。

有没有人考虑过以这种方式构建链的内存问题?

是。这是在承诺/ Aplus的讨论,例如,虽然没有结局呢。

许多承诺库确实支持迭代助手,以避免then诸如Bluebird eachmap方法之类的承诺链尖峰。

我自己的诺言库3,4确实具有解析链,而没有引入内存或运行时开销。当一个承诺采纳另一个承诺(即使仍未完成)时,它们就变得难以区分,中间的承诺不再在任何地方被引用。

承诺库之间的内存消耗会有所不同吗?

是。尽管这种情况可以优化,但很少如此。具体来说,ES6规范确实要求Promises在每次resolve通话时检查该值,因此无法折叠链。甚至可以用不同的值来解决链中的承诺(通过构造一个滥用吸气剂的示例对象,而不是现实生活中的对象)。该问题是在esdiscuss上提出的,但仍未解决。

因此,如果使用泄漏的实现,但需要异步递归,则最好切换回回调并使用延迟的反模式将最内层的Promise结果传播到单个结果Promise。

[1]:没有官方术语
[2]:嗯,它们是相互解决的。但是,我们 希望 用相同的值来解决这些问题,我们 期待 的是
[3]:无证操场,通过Aplus的。阅读代码的后果自负 :
[4]:在此pull请求中也为Creed实现

2020-04-25