我有一个带异步接口的递归函数,在调用时可能会超出堆栈深度限制:
function f(x, cb) {
if (x === 0) {
cb();
} else {
f(x - 1, cb);
}
}
f(1e6, function() {
console.log('done');
}); // BOOM
(是的,它必须是递归的,将其重写为迭代是不可行的。)
我可以通过异步执行递归调用来解决这个问题(例如通过setTimeout
或window.postMessage
,这应该更快):
function f(x, cb) {
if (x === 0) {
cb();
} else {
setTimeout(function() {
f(x - 1, cb);
}, 0);
}
}
f(1e6, function() {
console.log('done');
}); // ok
但这明显变慢了。所以我只想在异步调用会导致堆栈溢出时才进行异步调用。像
这样的东西function f(x, cb) {
if (x === 0) {
cb();
} else {
if (getCurrentStackDepth() == getMaxStackDepth() - 42)
setTimeout(function() {
f(x - 1, cb);
}, 0);
} else {
f(x - 1, cb);
}
}
}
或者,如果不可能,至少检测何时发生溢出,并异步重试。
的内容function f(x, cb) {
if (x === 0) {
cb();
} else {
try {
f(x - 1, cb);
} catch (e) {
if (isStackOverflowError(e)) {
setTimeout(function() {
f(x - 1, cb);
}, 0);
} else {
throw e;
}
}
}
}
如何做到这一点?通过Function.prototype.caller
解决方案是不可接受的,因为我处于es5-es6严格模式。我更喜欢便携式解决方案,但实际上只需要一个用于镀铬的解决方案。
答案 0 :(得分:1)
不幸的是,浏览器没有公开(JS)方法来检查调用堆栈大小,就像你说getCurrentStackDepth
一样。我试着找出它并且无法得到与之相关的任何信息。
最后一个解决方案可能是您解决此问题的方法之一。 JS引擎为堆栈大小抛出的异常对象的类型超过RangeError。因此,使用该信息和异常的消息信息,我们可以编写isStackOverflowError方法,如下所示
function isStackOverflowError(e) {
return (e instanceof RangeError) && /.+stack.+size.+/.test(e.message);
}
此外,与示例2相比,这具有更好的性能,但仍然不会太快。如果我找到更好的方法来解决这个问题,我会尝试更新。
答案 1 :(得分:0)
这是一种方法:
function f(x, cb) {
if (x === 0) {
cb();
} else {
try {
f(x - 1, cb);
} catch (e) {
setTimeout( function() { f(x-1, cb); }, 0 );
}
}
}
f(100000,function(){console.log("Done!")})
然而,堆栈溢出是一个编程错误,我不能真的建议这样解决它。将递归代码重构为迭代更好,因为它们在功能上是等价的。迭代版本将使用从堆分配的“堆栈”,这可能远远大于典型的进程堆栈。
有关详细信息,请参阅Recursion versus iteration。