当前位置: 首页 > 面试题库 >

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

高森
2023-03-14
问题内容

在这个答案中,一个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.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实现



 类似资料:
  • 问题内容: 我想遍历HTML 5文件系统中的所有文件,并在迭代完成后开始一些事件。由于这是异步+承诺,我很难尝试掌握其工作方式。 我正在使用angularJS,并创建了一个服务来封装html 5文件系统特定的功能。 这是递归函数: 理想情况下,我想这样调用该函数,并让其返回一个承诺,一旦遍历所有文件,该承诺便会执行。 有什么技巧/想法可以实现吗? 一个想法是拥有一个诺言数组,并为每个文件/目录向该

  • 问题内容: 我有一个像这样的递归函数 我正在使用它 我注意到您好永远不会返回,因为我怀疑我在递归调用上创建了多个promise,但是我不确定如何从中返回。 如何返回每个递归创建的Promise? 编辑: 结果是 问题答案: 递归是一种功能性遗产,因此将其与功能性样式一起使用可产生最佳效果。这意味着编写接受和操作其输入(而不是依赖于外部状态)和返回值(而不是依赖于突变或副作用)的函数。 你的程序,而

  • 问题内容: 我有一个整数id的数组,例如 并且我需要为每个ID执行异步远程调用。每个调用都是一个使用$ resource执行的WebAPI请求,并显示为Promise。 我需要创建一个接受这些ID数组的函数,然后初始化递归承诺链。该链应导致对每个ID依次进行webapi调用。这些调用不应并行,而应链接在一起。 有问题的函数返回自己一个“主要”的承诺,该承诺应根据异步Web调用的结果来解决或拒绝。也

  • 请检查下面的反转功能。剩下的代码应该没问题。由于某种原因,该函数没有反转双链接列表。 双链表节点结构 双链表结构 按从头部到尾部的顺序排列。 请检查下面的反向函数,因为此函数不会返回反向双链接列表。检查是否有任何错误并让我知道。

  • 问题内容: 我正在做的事情涉及按顺序运行(进行一些设置,然后运行调用者感兴趣的实际命令),然后进行一些清理)。 就像是: 哪里是这样的: 在内部,我使用,它返回,并且有效地将事件的结果从返回给调用者。 现在,我还需要将事件从stdout和stderr发送到调用者,这些事件也来自EventEmitters。是否有一种方法可以使(Bluebird)Promises正常工作,或者它们只是妨碍发出多个事件

  • 问题内容: 问题1:在给定的时间只允许一个API请求,因此,真正的网络请求在尚未完成的情况下排队。应用可以随时调用API级别,并且期望得到回报。当API调用排入队列时,将在将来的某个时刻创建对网络请求的承诺- 返回什么给应用程序?这样可以通过延迟的“代理”承诺来解决: 问题2:对一些API调用进行反跳动,以便随着时间的推移累积要发送的数据,然后在达到超时时分批发送。调用API的应用期望得到回报。