Roa*_*888 55 javascript recursion 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
}
Run Code Online (Sandbox Code Playgroud)
据推测,这会产生一个调用堆栈和一个承诺链 - 即"深"和"宽".
我预计内存峰值会大于执行递归或单独建立一个promise链.
Ber*_*rgi 44
调用堆栈和承诺链 - 即"深"和"宽".
实际上,没有.这里没有任何承诺链,因为我们知道它doSomeThingAsynchronous.then(doSomethingAsynchronous).then(doSomethingAsynchronous).…(如果以这种方式编写,那么顺序执行处理程序是什么Promise.each或者Promise.reduce可能做什么).
我们在这里面临的是一个解决方案链1 - 最终,当满足递归的基本情况时,会发生什么Promise.resolve(Promise.resolve(Promise.resolve(…))).如果你想称它为"深",而不是"宽".
我预计内存峰值会大于执行递归或单独建立一个promise链.
实际上并不是一个高峰.随着时间的推移,你会慢慢地构建大量的承诺,这些承诺用最里面的一个解决,所有的承诺都代表相同的结果.在任务结束时,条件得到满足并且最内层的承诺用实际值解决时,所有这些承诺都应该用相同的值来解决.这最终会O(n)带来走向解析链的成本(如果天真地实现,这甚至可以递归地完成并导致堆栈溢出).在那之后,除了最外面的所有承诺都可以变成垃圾收集.
相反,一个由类似的东西建立的承诺链
[…].reduce(function(prev, val) {
// successive execution of fn for all vals in array
return prev.then(() => fn(val));
}, Promise.resolve())
Run Code Online (Sandbox Code Playgroud)
会显示一个尖峰,同时分配npromise对象,然后逐个慢慢地解决它们,垃圾收集前一个,直到只有已确定的最终承诺存活.
memory
^ resolve promise "then" (tail)
| chain chain recursion
| /| |\
| / | | \
| / | | \
| ___/ |___ ___| \___ ___________
|
+----------------------------------------------> time
Run Code Online (Sandbox Code Playgroud)
是这样吗?
不必要.如上所述,该批量中的所有承诺最终都以相同的值2解析,因此我们所需要的只是一次存储最外层和最内层的承诺.所有中间承诺可能会尽快被垃圾收集,我们希望在恒定的空间和时间内运行此递归.
事实上,这种递归构造对于具有动态条件的异步循环(没有固定数量的步骤)是完全必要的,你无法真正避免它.在Haskell中,IOmonad 一直使用它,因为这种情况实现了对它的优化.它与尾调用递归非常相似,它通常由编译器消除.
有没有人考虑过以这种方式建立连锁店的记忆问题?
是.这是在承诺/ Aplus的讨论,例如,虽然没有结局呢.
许多promise库确实支持迭代助手,以避免then像Bluebird each和map方法那样的promise 链的飙升.
我自己的promise库3,4确实具有解析链,而不会引入内存或运行时开销.当一个承诺采用另一个承诺(即使仍然悬而未决)时,它们变得难以区分,并且中间承诺不再在任何地方引用.
承诺库之间的内存消耗会有所不同吗?
是.虽然这种情况可以优化,但很少.具体来说,ES6规范确实要求Promises在每次resolve调用时检查值,因此无法折叠链.链中的承诺甚至可以用不同的值来解决(通过构造滥用getter的示例对象,而不是在现实生活中).该问题在es escucuss上提出,但仍未得到解决.
因此,如果您使用泄漏实现,但需要异步递归,那么您最好切换回回调并使用延迟反模式将最内层的promise结果传播到单个结果承诺.
[1]:没有官方术语
[2]:嗯,他们是相互解决的.但是,我们希望用相同的值来解决这些问题,我们期待的是
[3]:无证操场,通过Aplus的.阅读代码是您自己的危险:https://github.com/bergus/F-Promise
[4]:也在此拉取请求中为Creed实现
Ben*_*aum 15
免责声明:过早优化是不好的,找出性能差异的真正方法是对您的代码进行基准测试,您不应该担心这一点(我只需要一次,我已经使用了至少100个项目的承诺) .
是这样吗?
是的,承诺必须"记住"他们所关注的内容,如果你为10000承诺执行此操作,你将拥有10000长的承诺链,如果你没有,那么你就不会(例如,递归) - 对于任何排队流量控制都是如此.
如果你必须跟踪10000个额外的东西(操作),那么你需要为它保留内存,这需要时间,如果这个数字是一百万,它可能是不可行的.这在图书馆中有所不同
有没有人考虑过以这种方式建立连锁店的记忆问题?
当然,这是一个很大的问题,也是一个用例,比如Promise.each像bluebird 这样的库,then可以用来链接.
我个人已经在我的代码中避免了这种风格的快速应用程序遍历VM中的所有文件 - 但在绝大多数情况下,这是一个非问题.
承诺库之间的内存消耗会有所不同吗?
是的,非常好.例如,如果bluebird 3.0检测到promise操作已经异步(例如,如果它以Promise.delay开头)并且只是同步执行事务(因为异步保证已经保留),蓝鸟3.0将不会分配额外的队列.
这意味着我在第一个问题的答案中声称的并不总是正确的(但在常规用例中也是如此):)除非提供内部支持,否则本机承诺将永远无法执行此操作.
然后再次 - 这并不奇怪,因为承诺库彼此之间存在数量级的差异.
我只是提出了一个可以帮助解决问题的技巧:不要在最后进行递归then,而应该在最后进行递归catch,因为catch它不在解决链范围内。以您的示例为例:
function foo() {
function doo() {
// always return a promise
if (/* more to do */) {
return doSomethingAsync().then(function(){
throw "next";
}).catch(function(err) {
if (err == "next") doo();
})
} else {
return Promise.resolve();
}
}
return doo(); // returns a promise
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
13388 次 |
| 最近记录: |