3

I am writing a jQuery plugin that lets users add references to their article. Basically, when the enter key is pressed while the user is focused on the reference input, the script checks the URL through my AJAX script to make sure that it's a working URL. If it's valid, a button is added to a list of references that the user can see. It will also update a hidden input that contains a comma-separated list of URL's.

I am very new to the concept of JS exceptions... I am currently getting an error saying Uncaught [object Object]. The error happens where I throw the variable 'info'. Any ideas?

(function($){
    $.fn.extend({
        references : function(options) {
            var defaults = {
                sample_div : '#sample-ref',
                remove_button : '#removereference',
                update_div : '#references',
                hidden_input : 'input[name="references"]',
                saved_input : 'input[name="saved-refs"]',
                ajax_url : 'ajax.php',
                search_input : '#reference'
            };        
            var options = $.extend(defaults, options);
            var count = 0;
            function addReferenceBlock(ref_title){
                var replacements = {
                    ref_title : ref_title,
                    ref_url : ref_url
                };
                var block = $(options.sample_div).html();
                $.each(replacements, function(index, value){
                    block.replace(new RegExp('{'+index+'}', 'g'), value);
               });
               $(options.update_div).append(block);
            }
            function checkReference(url){
                var postData = 'reference='+url;
                $.ajax(
                {
                    dataType: "xml",
                    type: "POST",
                    data : postData,
                    cache: false,
                    url: options.ajax_url
                })
                .done(function(xml, textStatus, jqXHR){
                    if(textStatus === 'success'){
                        $(xml).find('success').each(function(){     
                            console.log('checking '+url+'...');
                            var info = $(this).find('pagetitle');
                            throw info;
                        });
                        $(xml).find('error').each(function(){
                            throw false;
                            console.log($(this).find('message').text());
                        });
                    } else {
                        console.log(jqXHR);
                    }
                });
            }
            function init(element, options){
                $(options.search_input).enterKey(function(e){
                    try {
                        checkReference($(options.search_input).val());
                    } catch($status){
                        if($status !== false){
                            addReferenceBlock($status);
                            updateReferenceInput($(options.search_input).val());
                        } else {
                            alert($status);
                        }
                    }
                    e.preventDefault();
                });
            }
            return $(this).each(function(){ init(this, options); });
        }
    });
})(jQuery);
ShoeLace1291
  • 4,551
  • 12
  • 45
  • 81
  • Ajax is asynchronous. Just as you cannot [`return` from an ajax callback](http://stackoverflow.com/q/14220321/1048572), you cannot `throw` either. – Bergi Nov 18 '14 at 04:29

2 Answers2

3

Your try block calls the checkReference function. Your checkReference function calls done. done does not call the anonymous function which throws your error; it sets up an event handler so it can be called by the system later. Thus, your stack trace is not what you think it is.

EDIT

Why does "done" not call the code inside of it?

Because if it did, it would not be asynchronous. Let's mock this with setTimeout rather than AJAX, same principles apply:

function foo(how) {
  throw("In foo " + how + " (look at the stack trace by clicking on the triangle)");
}

function callFooAsynchronously() {
  console.log("calling foo asynchronously");
  setTimeout(function() {
    foo("asynchronously");
  }, 1000);
  console.log("finished calling foo asynchronously");
}

function callFooSynchronously() {
  console.log("calling foo synchronously");
  foo("synchronously");
  console.log("finished calling foo synchronously");
}

function main() {
  callFooAsynchronously();
  callFooSynchronously();
}

main();

The output is as follows:

calling foo asynchronously js:18
finished calling foo asynchronously js:22
calling foo synchronously js:26
Uncaught In foo synchronously (look at the stack trace by clicking on the triangle) js:14
  foo js:14
  callFooSynchronously js:27
  main js:34
  (anonymous function) js:37
Uncaught In foo asynchronously (look at the stack trace by clicking on the triangle) js:14
  foo js:14
  (anonymous function)

The synchronous call will start, then throw an exception. Due to the exception, "finished calling foo synchronously" is never displayed. The stack trace shows call from the snippet execution environment, calling to main, which calls callFooSynchronously, which, ultimately, calls foo.

The asynchronous call will display the starting message, attach a timeout handler, then display the finished message, then exit. This concludes callFooAsynchronously. One second later, the browser will remember there is something it needs to do, and this is reflected in the stack trace: the anonymous function passed to setTimeout is run, which in turn runs foo. Note how main and callFooAsynchronously are not a part of the stack trace: they have set the alarm, then left the building. callFooAsynchronously, despite its name, never calls foo, and neither does setTimeout.

The browser calls the anonymous function in setTimeout directly, just as it calls directly the onreadystatechange function on an XMLHttpRequest (the function that ultimately calls the function you passed to done), which is attached, but not called, by jQuery.ajax.

If done called your function, it would be executed immediately after you made the ajax call, and not when the response arrives, because that is when done gets executed.

Amadan
  • 191,408
  • 23
  • 240
  • 301
  • I don't quite follow when you say " it sets up an event handler so it can be called by the system later"... Why does "done" not call the code inside of it? – ShoeLace1291 Nov 18 '14 at 04:43
  • It's actually a misunderstanding that anything is done asnyc.... But I think over time the expression async became equivalent to "pushed to the stack after the delay" anyway +1 for the effort and a very good answer. – Max Bumaye Nov 18 '14 at 08:43
  • @MaxBumaye: Very true. It's obvious there can't be anything asynchronous in a single-threaded model, except for the things other systems do. But the term has stuck and people would look at me funny if I say "the continuation is enqueued" or similar. – Amadan Nov 18 '14 at 09:33
-1

In JS, you can throw errors using error-strings like: throw new Error('You did something wrong')

So in your case, maybe you can try: throw new Error(info.text()) which will fetch the text inside the .pagetitle element.

Is this what you want?

Riten Vagadiya
  • 123
  • 1
  • 8
  • So now my uncaught error is the content of the pagetitle tag from the xml... getting closer? – ShoeLace1291 Nov 18 '14 at 04:38
  • Sorry, I misread your question - I thought you wanted your error to display properly instead of "[Object object]". I believe you can follow what @Amadan has explained - quite detailed! – Riten Vagadiya Nov 18 '14 at 11:17