Working around browsers callstack size limitation

At the time of writing, I was working for a firm putting a lot of effort in developing AJAX apps for Fortune 500 companies. We write a lot of cool stuff and quite often have to face the limitations of our environment: the browser. We wrote an entire multi-pass parser for one of our in-house language, in JavaScript. This parser handles data in the scope of the current page, from the database, from cross-domain servers, it can import and iterate remote XML, HTML, SOAP, etc.. all that inline, as if you were manipulating local variables. With an interpreted parser this complex, a big enough chunk of code to parse without any data call, the recursion even finite would eventually create a call stack so big that most browser would break.

limitation

As of 2009, the browser with the largest call stack is Chrome. Of course you can't ask a Fortune 500 company to switch the browser on 10,000+ computers overnight, so we have to deal with the smallest call stack of all the browser: 100 for Safari.

solution

As long as you keep the operations synchronous and execute everything in line, recursion will eventually fill your call stack. The good news is that browsers have somewhat of a thread handling system. It is used by XHRs: the function on the callback will be the beginning of a new call stack, avoiding the size limit of the main call stack. Of course you can't just create an XHR just to split into a new thread.. fortunately we have another solution to run code in parallel: setTimeout.

measure your browser

Benchmark:

var max_process = 1;

function recurse_me() {
    max_process++;
    recurse_me();
}

try {
    recurse_me()
} catch(e) {
    alert("Dead at " + max_process)
}

solution example

Example:

var max_process = 0;

function recurse_me() {
    max_process++;
    if (max_process % 20 == 0) {
        setTimeout(recurse_me, 1);
    } else if (max_process >= 10000) {
        alert("Finished at " + max_process)
        return;
    } else {
        recurse_me();
    }
}

recurse_me();

Last updated: 2009-03-05