在javascript内存中递归构建承诺链的注意事项
在中,承诺链是递归构建的 稍微简化一下,我们有:在javascript内存中递归构建承诺链的注意事项,javascript,recursion,promise,Javascript,Recursion,Promise,在中,承诺链是递归构建的 稍微简化一下,我们有: function foo() { function doo() { // always return a promise if (/* more to do */) { return doSomethingAsync().then(doo); } else { return Promise.resolve(); } }
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 LIB之间的内存消耗会有所不同吗
Promise.each
的东西的一个用例,在像bluebird over这样的库中,每个都可以链接
我个人在我的代码中有一个避免这种风格的快速应用程序,它一次遍历VM中的所有文件-但在绝大多数情况下,这不是问题
promise LIB之间的内存消耗会有所不同吗
是的,非常好。例如,如果bluebird 3.0检测到承诺操作已经是异步的(例如,如果它以promise.delay开始),它将不分配额外的队列,而只是同步执行(因为异步保证已经被保留)
这意味着我在回答第一个问题时所说的并不总是正确的(但在常规用例中是正确的):“本机承诺”永远无法做到这一点,除非提供内部支持
再说一次——这并不奇怪,因为promise库之间存在数量级的差异
调用堆栈和承诺链-即“深”和“宽”
事实上,没有。我们从doSomeThingAsynchronous.then(doSomeThingAsynchronous.then(doSomeThingAsynchronous.then)(doSomeThingAsynchronous)…
(这就是promise.each
或promise.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())
将显示一个峰值,同时分配n
promise对象,然后缓慢地逐个解析它们,对以前的对象进行垃圾收集,直到只有已解决的end promise处于活动状态
memory
^ resolve promise "then" (tail)
| chain chain recursion
| /| |\
| / | | \
| / | | \
| ___/ |___ ___| \___ ___________
|
+----------------------------------------------> time
是这样吗
不一定。如上所述,所有的大量承诺最终都是用相同的值2解决的,因此我们所需要的就是同时存储最外层和最内层的承诺。所有中间承诺可能会尽快被垃圾收集,我们希望在恒定的空间和时间内运行此递归
事实上,对于动态条件(没有固定数量的步骤),这种递归构造是完全必要的,您无法真正避免它。在Haskell中,IO
monad一直使用这种方法,正是因为这种情况,才对它进行了优化。它非常类似于,编译器通常会消除它
有没有人考虑过以这种方式构建链的内存问题
对。例如,这是一个例子,尽管还没有结果
许多promise库确实支持迭代帮助器,以避免promisethen
链的尖峰,如Bluebird的each
和map
方法
我自己的promise Library 3,4在不引入内存或运行时开销的情况下提供解析链。当一个承诺采纳了另一个承诺(即使仍然悬而未决),它们就变得难以区分,中间承诺也不再在任何地方引用
promise LIB之间的内存消耗会有所不同吗
对。虽然这种情况可以优化,但很少优化。具体来说,ES6规范确实要求承诺在每次resolve
调用时检查值,因此不可能折叠链。链中的承诺甚至可以用不同的值来解析(通过构造滥用getter的示例对象,而不是在现实生活中)。但这个问题仍未解决
因此,如果您使用泄漏实现,但需要异步递归,那么最好切换回回调,并使用
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
}
const powerp = (base, exp) => exp === 0
? Promise.resolve(1)
: new Promise(res => setTimeout(res, 0, exp)).then(
exp => power(base, exp - 1).then(x => x * base)
);
powerp(2, 8); // Promise {...[[PromiseValue]]: 256}
// apply powerp with 2 and 8 and substitute the recursive case:
8 === 0 ? Promise.resolve(1) : new Promise(res => setTimeout(res, 0, 8)).then(
res => 7 === 0 ? Promise.resolve(1) : new Promise(res => setTimeout(res, 0, 7)).then(
res => 6 === 0 ? Promise.resolve(1) : new Promise(res => setTimeout(res, 0, 6)).then(
res => 5 === 0 ? Promise.resolve(1) : new Promise(res => setTimeout(res, 0, 5)).then(
res => 4 === 0 ? Promise.resolve(1) : new Promise(res => setTimeout(res, 0, 4)).then(
res => 3 === 0 ? Promise.resolve(1) : new Promise(res => setTimeout(res, 0, 3)).then(
res => 2 === 0 ? Promise.resolve(1) : new Promise(res => setTimeout(res, 0, 2)).then(
res => 1 === 0 ? Promise.resolve(1) : new Promise(res => setTimeout(res, 0, 1)).then(
res => Promise.resolve(1)
).then(x => x * 2)
).then(x => x * 2)
).then(x => x * 2)
).then(x => x * 2)
).then(x => x * 2)
).then(x => x * 2)
).then(x => x * 2)
).then(x => x * 2); // Promise {...[[PromiseValue]]: 256}
var funcA = function() {
setTimeout(function() {console.log("funcA")}, 2000);
};
var funcB = function() {
setTimeout(function() {console.log("funcB")}, 1000);
};
sequence().chain(funcA).chain(funcB).execute();