2015-10-23 42 views
4

我有一个异步接口的递归函数调用时,将有可能超过堆栈深度限制:如何捕获堆栈溢出错误

function f(x, cb) { 
    if (x === 0) { 
     cb(); 
    } else { 
     f(x - 1, cb); 
    } 
} 

f(1e6, function() { 
    console.log('done'); 
}); // BOOM 

(是的,它必须是递归的,重写它是反复被不可行)。

我可以通过异步操作的方式递归调用(例如,经由setTimeoutwindow.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

将递归重写为循环要容易得多。是的,你说它必须是递归的,但是当你使用'setTimeout'时,它不会再递归了。 – Kenney

+0

@Kenney这是一个例子,我实际上有大约十几个相互递归的异步分支函数。 –

+0

好的。使用'setTimeout'来避免堆栈溢出是一个不好的决定,恕我直言。你原来的函数基本归结为'for(; x> 0; x--){}; CB();'。 (顺便说一下,你确实意识到你的顶级函数不是异步的?) – Kenney

回答

1

不幸的是没有方法暴露(JS)的浏览器来检查调用堆栈的大小,就像你说的getCurrentStackDepth。我试图找出它,并没有得到任何有关它的信息。

最后的解决方案可以是您可以解决此问题的方法之一。 JS引擎为堆栈大小抛出的异常对象的类型超过了RangeError。因此,使用这些信息和异常的消息信息,我们可以写isStackOverflowError方法如下图所示

function isStackOverflowError(e) { 
    return (e instanceof RangeError) && /.+stack.+size.+/.test(e.message); 
} 

此外,这具有更好的性能相比,例如-2,但仍不能太快。如果我找到解决此问题的更好方法,我会尽力更新。

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了解更多信息。

+0

为什么downvote?任何建设性的反馈? – Kenney