Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call stack is lost when an exception is thrown within a jQuery handler

I'm having a problem debugging my web applications. It's very frustrating because I've been trying to reproduce the problem with a small web page that I can post on jsfiddle, but this seems to be a case of the "Higgs-Bugson".

I have a web page with a large jQuery(document).ready() handler. The problem is that when an exception is thrown from within the jQuery(document).ready() handler, I get a call-stack with several anonymous functions and no pointer to the code that actually threw the exception.

Whenever I try to reproduce this behavior with a small web page, I always get a pointer to the code that threw the exception, but in production code, I never get the stack pointer. This makes debugging more frustrating and error-prone.

Does anyone here any any idea what could be causing this behavior and how to make it right?


Update: It's been several months since I posted this question and I now believe that I have conclusively reproduced the issue. I reproduce the issue with the following HTML:

<html xmlns="http://www.w3.org/1999/xhtml" >    
<body>
Factorial Page
<input type='button' value='Factorial' id='btnFactorial' />
<script src="Scripts/jquery-1.6.1.js" type="text/javascript"></script>
<script type='text/javascript'>
    $(document).ready(documentReady);

    function documentReady() {
        $('#btnFactorial').click(btnFactorial_click);

        factorial(-1);
    }

    function btnFactorial_click() {
        var x;
        x = prompt('Enter a number to compute factorial for.', '');
        alert(x + '! = ' + factorial(x));
    }

    function factorial(x) {
        if (x < 0)
            throw new Error('factorial function doesn\'t support negative numbers');
        else if (x === 0 || x === 1)
            return 1;
        else
            return factorial(x - 1) * x;
    }
</script>       
</body>
</html>

Looking at the code, you'll see that when you click a button, the code alerts the factorial of a number that you specify. Enter a negative number and an error will be produced. In this case, Visual Studio will catch the error and highlight the line of code where the error occurred and display a call stack including factorial and btnFactorial_click.

This code also invokes factorial(-1) from the $(document).ready handler. In this case, Visual Studio moves the highlight to the finally block in the following jQuery code (excerpted from jquery-1.6.1.js, beginning on line 987

            // resolve with given context and args
            resolveWith: function( context, args ) {
                if ( !cancelled && !fired && !firing ) {
                    // make sure args are available (#8421)
                    args = args || [];
                    firing = 1;
                    try {
                        while( callbacks[ 0 ] ) {
                            callbacks.shift().apply( context, args );
                        }
                    }
                    finally {
                        fired = [ context, args ];
                        firing = 0;
                    }
                }
                return this;
            },

Although the call stack is not displayed, Visual Studio displays a pop-up showing the text of the error.

What is the reason for this odd behavior and how can I design my application so that I can easily trap errors that originate from handlers such as $(document).ready?

like image 480
Vivian River Avatar asked May 08 '12 14:05

Vivian River


3 Answers

I'm having a problem debugging my web applications

What type of debugger are you using? They tend to vary and the best one that I have found when developing is firefox's because of some very nice integrated tools they have.

Although it is nice to reproduce a problem, when it cannot be reproduced in a simple succinct example then debugging becomes more and more important. Setting breakpoints and going line by line is sometimes the only way instead of waiting for the error to occur during runtime and then tracing back.

There are several approaches outlined in this MSDN article which could be useful to helping solve your problem. I am sure you have tried some of them, but it is at least worth looking at - notably "Scenario 3: I am getting an error deep inside a set of helper methods, and I'm not sure where the error originated."

"How to Debug Your jQuery Code"

http://msdn.microsoft.com/en-us/magazine/ee819093.aspx

Edit in response to new content

jsfiddle of new content: http://jsfiddle.net/EJbsS/

When running the jsfiddle in chrome, the first run through factorial(-1) is called. An error appears. It can be accessed by clicking in the bottom right at the red x, or by inspecting element, navigating to the console tab, and then looking at the text area. The error properly reads "Uncaught Error: factorial function doesn't support negative numbers ", with a link to the exact line where throw new Error() is used.

Perhaps I need a little more clarification, what is the issue that you are trying to resolve? It seems that chrome debugs this script just fine. Perhaps there are some issues with Visual Studio's debugger.

Moreover

Here is a reproduction of a working page that can be used from notepad saved as .html

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>demo</title> 
<script type='text/javascript' src='http://code.jquery.com/jquery-1.6.4.js'></script>
<script type='text/javascript'>//<![CDATA[ 
$(window).load(function(){
$(document).ready(documentReady);
function documentReady() {
    $('#btnFactorial').click(btnFactorial_click);

    factorial(-1);
}

function btnFactorial_click() {
    var x;
    x = prompt('Enter a number to compute factorial for.', '');
    alert(x + '! = ' + factorial(x));
}

function factorial(x) {
    if (x < 0)
        throw new Error('factorial function doesn\'t support negative numbers');
    else if (x === 0 || x === 1)
        return 1;
    else
        return factorial(x - 1) * x;
}
});//]]>  
</script>
</head>
<body>
Factorial Page
<input type='button' value='Factorial' id='btnFactorial' />  
</body>
</html>

When running this page in google chrome, navigating to the sources tab, and setting a breakpoint at Line 26: if (x < 0) the entire call stack is reproduced when the page is run, on the right hand side.

factorial (debugpage.html:26)
documentReady (debugpage.html:16)
jQuery.extend._Deferred.deferred.resolveWith (jquery-1.6.4.js:1016)
jQuery.extend._Deferred.deferred.done (jquery-1.6.4.js:1002)
jQuery.fn.jQuery.ready (jquery-1.6.4.js:282)
(anonymous function) (debugpage.html:11)
jQuery.event.handle (jquery-1.6.4.js:3001)
jQuery.event.add.elemData.handle.eventHandle (jquery-1.6.4.js:2635)

Even further

Hopefully this caveat will be the best yet. Looking a little into stack tracing, I found this: console.trace();. It is very useful.

Insert it into the above code at line 26 like so:

if (x < 0)
    throw new Error('factorial function doesn\'t support negative numbers');
else if (x === 0 || x === 1)

Becomes

if (x < 0){
    console.trace();
    throw new Error('factorial function doesn\'t support negative numbers');
}else if (x === 0 || x === 1)

Running it now, without a breakpoint, will produce the stack trace in the console when the error condition is met:

console.trace() debugpage.html:27
factorial debugpage.html:27
documentReady debugpage.html:16
jQuery.extend._Deferred.deferred.resolveWith jquery-1.6.4.js:1016
jQuery.extend._Deferred.deferred.done jquery-1.6.4.js:1002
jQuery.fn.jQuery.ready jquery-1.6.4.js:282
(anonymous function) debugpage.html:11
jQuery.event.handle jquery-1.6.4.js:3001
jQuery.event.add.elemData.handle.eventHandle
like image 58
Travis J Avatar answered Nov 18 '22 11:11

Travis J


I would suggest creating an object which is instantiated within the jQuery(document).ready() block. For example:

var oStart = function(){
    var pageMethod = function(){ /*...*/ }
    var pageMethodOther = function(){ /*...*/ }
    /*...*/
    var init = function(){
        jQuery(document).ready(function(){
            /* do your stuff here */
        });
    }
}

oStart.init();

This will help you separate your application logic and the jQuery only logic. You should have very little in your ready block. Only those things which are absolutely necessary after the DOM has loaded completely. Everything else should be able to be loaded into memory while the page is still rendering.

like image 28
seangates Avatar answered Nov 18 '22 10:11

seangates


I tried your small example with the built-in IE debugger and it reported the exception message just fine, placing the cursor on the throw.

The same in Chrome.

I have jquery v1.6.2. Maybe worth an upgrade?

Then again, this smells like a threading issue. If it always happens in conjunction with a UI update (like the alert), it could be the VS debugger is looking at a UI server thread when it thinks it's in the js thread. In other words, a bug in the debugger. If this is the case, you should try a different debugger to see if the problem disappears.

like image 1
Gene Avatar answered Nov 18 '22 12:11

Gene