在javascript中递归建立一个promise链 - 内存考虑因素 [英] Building a promise chain recursively in javascript - memory considerations

查看:141
本文介绍了在javascript中递归建立一个promise链 - 内存考虑因素的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此答案中,以递归方式构建承诺链。

In this answer, a promise chain is built recursively.

稍微简化,我们有:

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
}

大概这会产生一个调用堆栈承诺链 - 即深和宽。

Presumably this would give rise to a call stack and a promise chain - ie "deep" and "wide".

我预计内存峰值会大于执行递归或单独建立一个承诺链。

I would anticipate a memory spike larger than either performing a recursion or building a promise chain alone.


  • 是这样吗?

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

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

推荐答案


一个调用堆栈和一个承诺链 - 即深和宽 。

a call stack and a promise chain - ie "deep" and "wide".

实际上,没有。这里没有承诺链,因为我们从 doSomeThingAsynchronous.then(doSomethingAsynchronous).then(doSomethingAsynchronous)知道它。... (这是 Promise.each Promise.reduce 如果以这种方式编写,可能会顺序执行处理程序。)

Actually, no. There is no promise chain here as we know it from doSomeThingAsynchronous.then(doSomethingAsynchronous).then(doSomethingAsynchronous).… (which is what Promise.each or Promise.reduce might do to sequentially execute handlers if it was written this way).

我们在这里面临的是一个解析链 1 - 最终会发生什么事情,当满足递归的基本情况时,就像 Promise.resolve(Promise.resolve(Promise.resolve(...)))。这只是深度,而不是宽,如果你想把它称之为。

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.


我预计内存峰值大于单独执行递归或构建承诺链。

I would anticipate a memory spike larger than either performing a recursion or building a promise chain alone.

实际上不是峰值。随着时间的推移,你会慢慢地构建大量的承诺,这些承诺用最里面的一个解决,所有的承诺都代表相同的结果。在任务结束时,条件得到满足并且最内层的承诺用实际值解决时,所有这些承诺都应该用相同的值来解决。这最终会带来 O(n)走向解析链的成本(如果天真地实现,这甚至可以递归地完成并导致堆栈溢出)。在那之后,除了最外面的所有承诺都可以变成垃圾收集。

Not a spike actually. You'd slowly, over time, build a bulk of promises that are resolved with the innermost one, all representing the same result. When, at the end of your task, the condition is fulfilled and the innermost promise resolved with an actual value, all of these promises should be resolved with the same value. That would end up with O(n) cost for walking up the resolve chain (if implemented naively, this might even be done recursively and cause a stack overflow). After that, all the promises except for the outermost can become garbage collected.

相比之下,一个由

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

会显示一个峰值,同时分配 n promise对象,然后逐个慢慢地解析它们,垃圾收集前一个,直到只有结算的最终承诺是活着的。

would show a spike, allocating n promise objects at the same time, and then slowly resolve them one by one, garbage-collecting the previous ones until only the settled end promise is alive.

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




是这样吗?

Is this so?

不一定。如上所述,该批量中的所有承诺最终都使用相同的值 2 来解决,因此我们所需要的只是一次存储最外层和最内层的承诺。所有中间承诺可能会尽快被垃圾收集,我们希望在恒定的空间和时间内运行此递归。

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.

实际上,这个递归构造是完全必要的对于具有动态条件的异步循环(没有固定数量的步骤),您无法真正避免它。在Haskell中, IO monad一直使用它,因为这种情况实现了对它的优化。它与尾调用递归非常相似,编译器通常会将其删除。

In fact, this recursive construct is totally necessary for asynchronous loops with a dynamic condition (no fixed number of steps), you cannot really avoid it. In Haskell, where this is used all the time for the IO monad, an optimisation for it is implemented just because of this case. It is very similar to tail call recursion, which is routinely eliminated by compilers.


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

Has anyone considered the memory issues of building a chain in this way?

是的。例如,在promises / aplus 讨论过,但尚无结果。

Yes. This was discussed at promises/aplus for example, though with no outcome yet.

许多承诺库都支持迭代助手,以避免承诺然后链的峰值,如Bluebird的每个地图方法。

Many promise libraries do support iteration helpers to avoid the spike of promise then chains, like Bluebird's each and map methods.

我自己的承诺库 3,4 确实在不引入内存或运行时开销的情况下解析链。当一个承诺采用另一个承诺时(即使仍然未决),它们变得难以区分,并且中间承诺不再在任何地方被引用。

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.


内存消耗承诺库之间有什么不同?

Would memory consumption differ between promise libs?

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

Yes. While this case can be optimised, it seldom is. Specifically, the ES6 spec does require Promises to inspect the value at every resolve call, so collapsing the chain is not possible. The promises in the chain might even be resolved with different values (by constructing an example object that abuses getters, not in real life). The issue was raised on esdiscuss but remains unresolved.

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

So if you use a leaking implementation, but need asynchronous recursion, then you better switch back to callbacks and use the deferred antipattern to propagate the innermost promise result to a single result promise.

[1]:没有官方术语

[2]:嗯,他们彼此解决了。但我们想要以相同的价值解决它们,我们期待那个

[3]:无证件的游乐场,传递aplus。阅读代码需要您自担风险: https://github.com/bergus/F-Promise

[4]:也在此拉取请求中为Creed实施

[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
[4]: also implemented for Creed in this pull request

这篇关于在javascript中递归建立一个promise链 - 内存考虑因素的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆