yuri kilochek
yuri kilochek

Reputation: 13486

How to trap stack overflow error

I have an recursive function with asynchronous interface that will likely exceed stack depth limits when called:

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

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

(and yes, it has to be recursive, rewriting it to be iterative is not viable).

I can solve this by doing recursive call asynchronously (e.g. via setTimeout or window.postMessage, which is supposedly faster):

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

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

But this is significantly slower. So I want to do the asynchronous call only when it would otherwise cause a stack overflow. Something like

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);
        }
    }
}

or, if this is not possible, at least detect when an overflow happens, and retry asynchronously. Something along the lines of

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;
            }
        }
    }
}

How can this be done? Solution via Function.prototype.caller is not acceptable, as I am in es5-es6 strict mode. I would prefer a portable solution, but really only need one for chrome.

Upvotes: 4

Views: 250

Answers (2)

Kenney
Kenney

Reputation: 9093

This is one way to do it:

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!")})

However, a stack overflow is a programming error, and I can't really recommend solving it this way. It is far better to refactor the recursive code to be iterative, as they are functionally equivalent. The iterative version would use a 'stack' allocated from the heap, which can be far larger than a typical process stack.

See Recursion versus iteration for more information.

Upvotes: 0

rajuGT
rajuGT

Reputation: 6404

Unfortunately there is no method exposed(JS) by browser to check call stack size, like you said getCurrentStackDepth. I tried finding it out and couldn't get any information related to it.

The last solution can be one of the way you can solve this issue. The typeof of exception object thrown by JS engine for stack size exceed is RangeError. So using that information and exception's message information, we can write isStackOverflowError method as shown below

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

Also, this has better performance compared to example-2, but still not too fast. I will try to update if I find better way to solve this issue.

Upvotes: 1

Related Questions