Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jQuery AJAX problems, intermittently getting duplicate requests

Right then, I'm coming to the end of a rewrite of our JavaScript system, we're moving from Prototype to jQuery. We have a load of AJAX requests that fire when a certain element events occur, the one in the example below is a new event on a calendar, but it happens elsewhere too.

The problem I'm getting is when an event is fired sometimes two AJAX requests are made. The first one returns the correct value but (as you can see) it still says processing, it never returns the success message my JavaScript requires. The second request returns the correct result AND terminates correctly.

The problem I have is our jQuery screen blocker is set up to prevent user input during heavy AJAX requests, obviously because there an AJAX request still live the screen never unlocks. If I were to refresh this screen all will work as desired.

Can anyone shed any light on why this behavior is occuring.

alt text http://archive.ilmv.co.uk/images/jquery-duplicate-ajax-request.png

EDIT

"requests that fire when a certain element events occur" - this is the key phrase, I think. Please provide some info on how you set your events. Maybe it fires twice because you have multiple handlers set? – Igor Zinov'yev

Ok, the thing is when I hit refresh the problem usually resolves itself, so not sure how that could be a handler issue, here's the event handler we typically use for a change of a select box.

$("#my_select","#context").change(function(){
            // uz_ajax is a wrapper function, keeps most of the same functionality as $.ajax()
    uz_ajax({
        target:"#my_target",
        data:{
            module:'my_module',
            controller:'my_controller',
            action:'my_action',
            id:$(this).val(),
            ajax:''
        }
    });
});

The problem I have is I have no idea how to replicate the problem, so I do not know if the event is being fired multiple times or whether the AJAX is requesting twice.

EDIT 2

If you are reloading the elements that are bound then the change events could be triggered recursively... We really need to see more code (what do you do on success of the ajax call etc) – redsquare

Thanks redsquare, upon a successful AJAX request I usually apply the response (usually HTML of <option>s) to the target. I never trigger a change from the element that fired the AJAX, but I sometimes trigger a change on the target element, to allow for cascading AJAX requests. If this were the problem, surely this would happen all the time? My wrapper function uz_ajax is below:

var ajax_count = 0;
var ajax_response = null;

function uz_ajax(options) {

    // set default options
    var defaults = {
        async: true,
        type: "GET",
        data: {},
        url: "/",
        cache: false,
        force_change: true,
        highlight: true,
        action: "normal",
        dataType: "html",
        selected: {
            value: null,
            disabled: false
        }
    };

    // merge the passed options with the defaults
    var options = $.extend(defaults, options);

    // start the jQuery ajax method
    $.ajax({
        async: options.async,
        type: options.type,
        url: options.url,
        data: options.data,
        beforeSend: function() {
            // we only want to block the screen on the first pass
            if(++ajax_count==1) {
                $.blockUI({message:'Loading data, please wait'});
            } 
        },
        success: function(responseText) {           
            if(options.target!==undefined) {
                // if target isn't an array, make it one
                if(!$.isArray(options.target)) {
                    options.target = new Array(options.target);
                }

                var targets = options.target;

                for ( var i in targets ) {
                    console_info("uz_ajax() --> Applying contents to "+targets[i]);

                    switch(options.action) {
                        case "normal":
                            if($(targets[i]).is("input")) {
                                $(targets[i]).val(trim(responseText));
                            } else {
                                $(targets[i]).html(trim(responseText));
                            }
                            break;
                        case "selected":
                            // preserve the current target value (e.g. list of options), but 
                            // set the selected value to the ajax response
                            console_warn("Changing selected value of "+targets[i]+" to '"+responseText+"'");
                            // trim the response so we don't get any smarty induced errors such as '    7'
                            $(targets[i]).val(trim(responseText));
                            break;
                    }

                    // set selected value
                    if(options.selected.value!=null) {
                        $(targets[i]).val(options.selected.value);
                    }

                    // highlight the target
                    // we don't want to highlight the target if it's a hidden input, as this will force a .show(
                    if($(targets[i]).attr('type')!='hidden' && options.highlight===true) {
                        $(targets[i]).effect("highlight",{},2000);
                    }

                    // force the target to change
                    if(options.force_change===true) {
                        $(targets[i]).trigger("change");    
                    }

                    /* rebind certain elements that do not use conventional events */
                    /* We probably need to get all of these rebinds in a single function */
                    createDatePickers(targets[i]);
                }
            } else {
                ajax_response = responseText;
                console_warn("uz_ajax -> no targets specified");
                // Well... we have no element to target, we need to return the value instead
                // of course if we return here we're going
                // we probably also need to check the ajax count as this will be the last executed part before we return
            }
        },
        complete: function () {
            /* if all ajax requests have completed, unblock screen */
            if(--ajax_count===0) {
                $.unblockUI();
                /* could use this callBack to return a value *dun dun duuuuun* */
                if (options.ajaxComplete) {
                    options.ajaxComplete(ajax_response);
                }
            }
        },
        cache: options.cache,
        dataType: options.dataType
    });

}

another way to stop multiple ajax requests is to heck jQuery.active prior to the call. jQuery keeps an internal count of 'live' ajax requests using this property. – redsquare

I'll look into this.

EDIT 3

So this is the result of the $('element').data(), but I can't understand what it's trying to say, does this mean there are two binds to it. If so how do I find out what these binds are and why do they both not fire when the event is fired.

alt text http://archive.ilmv.co.uk/images/firebug-jquery-data-events.png

EDIT 4

Here's another screenshot of the problem, this time in a different place in the system. The green arrow is the element thats triggers the three ajax requests, not in firebug how there's six, and that they pair up and share timestamps down to the millisecond?

The top three are the ones that have not completed, the bottom three have return the correct result.

alt text http://archive.ilmv.co.uk/images/jquery-duplicate-ajax-request-v2.png

EDIT 5

The image below demonstrates what happens when a select box is changed, it fires three different ajax requests.

I've managed to do a bit more debugging and I can tell you the following:

  1. The problem occurs regardless of how many ajax requests the initial element calls
  2. The problem occurs regardless of what plugins are used
  3. This event is definitely only being fired once
  4. My uz_ajax wrapper function is only being fired once per target (see the firebug console, "Line 29: uz_ajax()".
  5. I've added console.info on each of the jquery $.ajax() callbacks to see what's firing and when, notice how the only callback to fire is beforeSend().

alt text http://archive.ilmv.co.uk/images/jquery-duplicate-ajax-request-v3.png

EDIT 6

Right then, after @redsquare suggested via Twitter about using console.trace() to find out "where the second event is firing from" is, like I've always maintained I'm sure there isn't two events firing so I put the trace in the $.ajax() method, here's what happened:

alt text http://archive.ilmv.co.uk/images/jquery-duplicate-ajax-request-v4.png

As you can see I get the duplicate ajax request problem even though the $.ajax() method has only fired once, again the timestamps are identical. Have I come across a bug with jQuery?

EDIT 7

It happens on StackOverflow too!

alt text http://archive.ilmv.co.uk/images/jquery-duplicate-ajax-request-v5.png

like image 753
Ben Everard Avatar asked Jul 01 '10 08:07

Ben Everard


2 Answers

Have you tried using a .click() instead? I have had issues with irregular activity using .change() in javascript. Give that a try.

like image 135
Phil Avatar answered Oct 05 '22 11:10

Phil


Right, so I've come to a conclusion, I'm pretty sure that my problem is an issue with Firebug, as I've tested it the app on FF without Firebug turned on and it works as it should do.

This isn't the first time something JavaScript related has led me on a merry dance when the symptoms thrown by the problem don't offer any reasonable hint to the solution, but there we have it.

Massive thanks to @redsquare, who's helped massively both here, but via twitter and the jQuery IRC channel, many thanks dude!

like image 29
Ben Everard Avatar answered Oct 05 '22 11:10

Ben Everard