AjaxQueue and jQuery 1.3

800px-Queue_algorithmn The website that I work on for the majority of my time had been running on the rather out of date jQuery 1.2 for quite some time. I recently found myself with a little bit of downtime and decided it was a good time to see if we could upgrade and take advantage of all the speed improvements that the latest version gives us.

On the most part, the upgrade was fairly painless. Most things worked without change but one particular plug-in did not – AjaxQueue. This is a great little control that acts as an add-on to the ajax method of jQuery, allowing a little more control over how concurrent calls are processed. It gives three modes of operation but for us, the most useful of these are abort and queue and from the names it is fairly obvious what they do.

After I upgraded to jQuery 1.3, it did not take me long to realise that the queue mode was broken. I briefly looked around for an alternative plug-in but found that all of them would require too much change to our existing controls. There was no alternative, I had to open up the hood of AjaxQueue and see what had gone wrong.

What I found was that there must of been a change to a method that the plug-in relied on – a method that it seemed, only became publicly available in version 1.3 – queue. Although the function was similar, the conclusion that I came to was that prior to version 1.3, the queue function automatically de-queued any function added and processed it. When using the latest jQuery, the ajax calls were queued but there was no code to start the processing of the queue.

It was slightly more complex I thought it would be to get it going – I had to ensure the queue was only started once. I finally came up with a solution and a newly revived plug-in, the code of which is below. As the original plugin has not been touched for quite some time (2007), I thought it ok to post my modified version here.

(function($) {
 
    var ajax = $.ajax,
        pendingRequests = {},
        synced = [],
        syncedData = [],
        ajaxRunning = [];
 
 
    $.ajax = function(settings) {
        // create settings for compatibility with ajaxSetup
        settings = jQuery.extend(settings, jQuery.extend({}, jQuery.ajaxSettings, settings));
 
        var port = settings.port;
 
        switch (settings.mode) {
            case "abort":
                if (pendingRequests[port]) {
                    pendingRequests[port].abort();
                }
                return pendingRequests[port] = ajax.apply(this, arguments);
            case "queue":
                var _old = settings.complete;
                settings.complete = function() {
                    if (_old) {
                        _old.apply(this, arguments);
                    }
                    if (jQuery([ajax]).queue("ajax" + port).length > 0) {
                        jQuery([ajax]).dequeue("ajax" + port);
                    } else {
                        ajaxRunning[port] = false;
                    }
                };
 
                jQuery([ajax]).queue("ajax" + port, function() {
                    ajax(settings);
                });
 
                if (jQuery([ajax]).queue("ajax" + port).length == 1 && !ajaxRunning[port]) {
                    ajaxRunning[port] = true;
                    jQuery([ajax]).dequeue("ajax" + port);
                }
 
                return;
            case "sync":
                var pos = synced.length;
 
                synced[pos] = {
                    error: settings.error,
                    success: settings.success,
                    complete: settings.complete,
                    done: false
                };
 
                syncedData[pos] = {
                    error: [],
                    success: [],
                    complete: []
                };
 
                settings.error = function() { syncedData[pos].error = arguments; };
                settings.success = function() { syncedData[pos].success = arguments; };
                settings.complete = function() {
                    syncedData[pos].complete = arguments;
                    synced[pos].done = true;
 
                    if (pos == 0 || !synced[pos - 1])
                        for (var i = pos; i < synced.length && synced[i].done; i++) {
                        if (synced[i].error) synced[i].error.apply(jQuery, syncedData[i].error);
                        if (synced[i].success) synced[i].success.apply(jQuery, syncedData[i].success);
                        if (synced[i].complete) synced[i].complete.apply(jQuery, syncedData[i].complete);
 
                        synced[i] = null;
                        syncedData[i] = null;
                    }
                };
        }
        return ajax.apply(this, arguments);
    };
 
})(jQuery);
 

29 thoughts on “AjaxQueue and jQuery 1.3

  1. Wondering im using drupal with jquery 1.3 and i would like to apply this function it would be a lifesaver
    Can anyone give an example of a request called using this plugin?
    thx in advance

  2. Actually yes, but I am unsure as to how to reproduce it. I simply.. used it.

    Granted, I used it on a site-wide level to define all ajax calls as “abort”, so that might have been it? I’ll try and whip up an example today.

  3. @swader I threw together a quick test for using jQuery 1.5 and it seemed to work correctly. Have you found a situation in which the plugin does not work with jQuery 1.5? I am keen to know.

  4. @swader I have yet to find the time to update this plugin for the new versions of jQuery. The entire ajax functionality has been rewritten, so I am unsure yet what needs to be done to make it work. I will need to update it for projects I am working on, so I’ll post it here as soon as I have done it.

  5. @Swader I have added a subscribe checkbox to the comments form. Hope that helps. In answer to your previous question – the calls are held on a queue. You could empty the queue manually with something like: jQuery([ajax]).clearQueue(“ajaxTHEPORT”). Replace ‘THEPORT’ with whatever your port is named. I have not tested this but I think it should work.

  6. Is.. umm.. is it possible to subscribe to the comments so I know when and if someone answers my comment?

  7. Is there a way to cancel the queue?
    For example, hundreds of calls are going on… a user notices an error in one (as the statues are being updated on screen live) and wants to stop the queue completely and empty it. Can it be done?

  8. edit: at least for the “queue” mode it can be fixed if you
    change all calls of “ajax.apply(this, arguments)” to
    ajax.apply(settings.context, arguments) The “sync” mode seems to be
    more work…

  9. I have a question: is there anyway to pause, or cancel the queued ajax requests from another js call?

    Let say I have to send 10 heavy requests, at request no 5, I want to pause those heavy requests or even abort them, to send another one.

    Do you have an idea?
    Tuan Jinn

  10. You rocks, it fix my errors, the queue-related issues has made everything stop working. I dont know what actually happened since it had worked the day before. It seemed that we updated jQuery and problem occurred.
    Thanks

  11. For anyone using the sync mode, the callbacks are being fired regardless of the response status. That is, the “success” callback will be called even if there’s an error (eg, request timed out).

    To fix this, change the following 3 lines (within the sync’s “for” loop:

    if ( synced[i].error && syncedData[i].error.length) synced[i].error.apply( jQuery, syncedData[i].error );
    if ( synced[i].success && syncedData[i].success.length) synced[i].success.apply( jQuery, syncedData[i].success );
    if ( synced[i].complete && syncedData[i].complete.length) synced[i].complete.apply( jQuery, syncedData[i].complete );

    Basically adding syncedData[i].*CALLBACK*.length to the if.

    HTH

  12. edit:
    at least for the “queue” mode it can be fixed if you change all calls of “ajax.apply(this, arguments)” to ajax.apply(settings.context, arguments)
    The “sync” mode seems to be more work…

  13. There is a bug when using the “context” option from the original jQuery.ajax . In the callbacks the false “this” reference is set.

  14. I’ve been looking high and low through all kinds of Ajax queuing stuff and this looks promising, but I have a page I’m working on that can have up to 8 Ajax requests, but the user determines how many requests there are. Is there a way with something like this to know when the queue has finished running?

  15. Hi Dan,

    Nice work!

    I use your solution on my local website, I use jQuery 1.4.2. There is a problem on your solution, the queue length can increase but never decrease, so it’s work for all ajax request which are sent before the first request is completed otherwise it’s blocked. It’s due to the fact that you use the queue length to permut the “ajaxRunning[port]” variable. But the queue length doesn’t reflact the number of request which are not yet sent but reflact the number of ajax request call.

    Anyway to solve it?

  16. Yes, thats exactly what it does. It also adds a ‘port’ option, which allows you to group requests together.

  17. So, do I understand correctly that this would add a new option ‘mode’ to standard .ajax() calls, to which I should pass either ‘abort’, ‘queue’ or ‘sync’?

  18. Thank you! I was having the usual AJAX race condition related problems, and all I had to do was simply drop this in, and it fixed it (the autocomplete plugin we use, the “Bassistance” one, already takes advantage of AJAX queue).

  19. @Michael Yes, adding the additional ‘mode’ option is what the original ajaxqueue plugin did. It also added the ‘port’ setting, so you can distinguish between different ajax calls.

  20. So, do I understand correctly that this would add a new option ‘mode’ to standard .ajax() calls, to which I should pass either ‘abort’, ‘queue’ or ‘sync’?

  21. Thank! just what I needed. I got as far as understanding that the queue was not starting to be processed and you came just in time for me.

    A well done job it is.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.