Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can jQuery deferreds be cancelled?

People also ask

Can JQuery be Deferred?

This JQuery. Deferred() method in JQuery is a function which returns the utility object with methods which can register multiple callbacks to queues. It calls the callback queues, and relay the success or failure state of any synchronous or asynchronous function.

What is the difference between a Deferred and a Promise?

A promise represents a value that is not yet known. This can better be understood as a proxy for a value not necessarily known when the promise is created. A deferred represents work that is not yet finished. A deferred (which generally extends Promise) can resolve itself, while a promise might not be able to do so.

What is the use of Deferred?

Deferred( [beforeStart ] )Returns: Deferred. Description: A factory function that returns a chainable utility object with methods to register multiple callbacks into callback queues, invoke callback queues, and relay the success or failure state of any synchronous or asynchronous function.

What is Deferred and Promise in JQuery?

A deferred object is an object that can create a promise and change its state to resolved or rejected . Deferreds are typically used if you write your own function and want to provide a promise to the calling code. You are the producer of the value. A promise is, as the name says, a promise about future value.


Looking in the jQuery doc and code, I don't see any way to cancel a jQuery deferred.

Instead, you probably need a way in your resolveWith handler to know that a subsequent ajax call has already been fired and this ajax call should ignore its result. You could do that with a globally incrementing counter. At the start of your ajax call, you increment the counter and then you grab the value into a local variable or put it as a property on the ajax object. In your resolveWith handler, you check to see if the counter still has the same value as when your ajax call started. If not, you ignore the result. If it does, no new ajax calls have been fired so you can process the result.

Alternately, you could refuse to fire a new ajax call while one is in flight already so you never had more than one in flight at a time. When the one finishes, you could either just use that result or fire the next one if desired.


While you can't "cancel" a deferred like you want, you could create a simple closure to keep track of the last ajax call through $.ajax returning an jqXHR object. By doing this you can simply abort() the call when a new jqXHR comes in to play if the last one wasn't finished. In your code's case it will reject the jqXHR and leave the deferred open to be deleted as you initially wanted.

var api = (function() {
    var jqXHR = null;

    return function(options) {
        var url = options.url;

        if (jqXHR && jqXHR.state() === 'pending') {
            //Calls any error / fail callbacks of jqXHR
            jqXHR.abort();
        }

        var deferred = $.Deferred(function() {
            this.done(options.success);
            this.fail(options.error);
        });

        jqXHR = $.ajax({
             url: url,
             data: options.toSend,
             dataType: 'jsonp'
        });

        jqXHR.done(function(data, textStatus, jqXHR) {
            if (data.f && data.f !== "false") {
                deferred.resolve();
            } else {
                deferred.reject();
            }
        });

        //http://api.jquery.com/deferred.promise/  
        //keeps deferred's state from being changed outside this scope      
        return deferred.promise();
    };
})();

I've posted this on jsfiddle. If you wish to test it out. Set timeout is used in combination with jsfiddles delayer to simulate a call being interupted. You'll need a console enabled browser to see the logs.

On a side note switch any .success(), .error(), and complete() methods over to deferred methods done(), fail(), and always(). Via jquery/ajax

Deprecation Notice: The jqXHR.success(), jqXHR.error(), and jqXHR.complete() callbacks will be deprecated in jQuery 1.8. To prepare your code for their eventual removal, use jqXHR.done(), jqXHR.fail(), and jqXHR.always() instead as newer


JustinY: seems like you're really close already to what you want. You're already using two deferreds (inner- > the ajax and outer -> $.Deferred()). You're then using the inner deferred to decide how to resolve the outer deferred based on some conditions.

Well, so just don't resolve the outer deferred at all when you don't want to (maybe you have a boolean variable that serves as a toggle gate for allowing the inner dfd to resolve/reject at all). Nothing bad will happen: whatever handlers you have attached to this entire function won't fire. Example in your inner success function:

if(gateOpen){
  gateOpen = false;
  if(hasStatus(jsonReturn, 'code', 200)) {
    deferred.resolveWith(this, [jsonReturn]);
  }
  else {
    deferred.rejectWith(this, [jsonReturn]);
  }
}

Some other logic in the application will decide when the gateOpen gets set back to true (some sort of _.throttle() or _.debounce() timeout, user interaction, whatever you want).If you wanted to track or cancel other requests in the else of that function, you could do that too. But the basic thing is that you don't have to resolve OR reject that outer deferred. And that's the same as canceling it, even if you don't cancel/abort the inner one.


I've created a shim that seamlessly adds the ability to cancel deferred objects and ajax requests.

In short, once a deferred object has been canceled, resolutions/rejections are completely ignored, and the state becomes "canceled".

According to jQuery.com, "Once the object has entered the resolved or rejected state, it stays in that state." Therefore, attempts to cancel are ignored once a deferred object is resolved or rejected.

(function () {
    originals = {
        deferred: $.Deferred,
        ajax: $.ajax
    };

    $.Deferred = function () {

        var dfr = originals.deferred(),
            cancel_dfr = originals.deferred();

        dfr.canceled = false;

        return {
            cancel: function () {
                if (dfr.state() == 'pending') {
                    dfr.canceled = true;
                    cancel_dfr.resolve.apply(this, arguments);
                }
                return this;
            },

            canceled: cancel_dfr.done,

            resolve: function () {
                if ( ! dfr.canceled) {
                    dfr.resolve.apply(dfr, arguments);
                    return this;
                }
            },

            resolveWith: function () {
                if ( ! dfr.canceled) {
                    dfr.resolveWith.apply(dfr, arguments);
                    return this;
                }
            },

            reject: function () {
                if ( ! dfr.canceled) {
                    dfr.reject.apply(dfr, arguments);
                    return this;
                }
            },

            rejectWith: function () {
                if ( ! dfr.canceled) {
                    dfr.rejectWith.apply(dfr, arguments);
                    return this;
                }
            },

            notify: function () {
                if ( ! dfr.canceled) {
                    dfr.notify.apply(dfr, arguments);
                    return this;
                }
            },

            notifyWith: function () {
                if ( ! dfr.canceled) {
                    dfr.notifyWith.apply(dfr, arguments);
                    return this;
                }
            },

            state: function () {
                if (dfr.canceled) {
                    return "canceled";
                } else {
                    return dfr.state();
                }
            },

            always   : dfr.always,
            then     : dfr.then,
            promise  : dfr.promise,
            pipe     : dfr.pipe,
            done     : dfr.done,
            fail     : dfr.fail,
            progress : dfr.progress
        };
    };


    $.ajax = function () {

        var dfr = $.Deferred(),
            ajax_call = originals.ajax.apply(this, arguments)
                .done(dfr.resolve)
                .fail(dfr.reject),

            newAjax = {},

            ajax_keys = [
                "getResponseHeader",
                "getAllResponseHeaders",
                "setRequestHeader",
                "overrideMimeType",
                "statusCode",
                "abort"
            ],

            dfr_keys = [
                "always",
                "pipe",
                "progress",
                "then",
                "cancel",
                "state",
                "fail",
                "promise",
                "done",
                "canceled"
            ];

        _.forEach(ajax_keys, function (key) {
            newAjax[key] = ajax_call[key];
        });

        _.forEach(dfr_keys, function (key) {
            newAjax[key] = dfr[key];
        });

        newAjax.success = dfr.done;
        newAjax.error = dfr.fail;
        newAjax.complete = dfr.always;

        Object.defineProperty(newAjax, 'readyState', {
            enumerable: true,
            get: function () {
                return ajax_call.readyState;
            },
            set: function (val) {
                ajax_call.readyState = val;
            }
        });

        Object.defineProperty(newAjax, 'status', {
            enumerable: true,
            get: function () {
                return ajax_call.status;
            },
            set: function (val) {
                ajax_call.status = val;
            }
        });

        Object.defineProperty(newAjax, 'statusText', {
            enumerable: true,
            get: function () {
                return ajax_call.statusText;
            },
            set: function (val) {
                ajax_call.statusText = val;
            }
        });

        // canceling an ajax request should also abort the call
        newAjax.canceled(ajax_call.abort);

        return newAjax;
    };
});

Once added, you may cancel an ajax call:

var a = $.ajax({
        url: '//example.com/service/'
    });

a.cancel('the request was canceled');

// Now, any resolutions or rejections are ignored, and the network request is dropped.

..or a simple deferred object:

var dfr = $.Deferred();

dfr
    .done(function () {
        console.log('Done!');
    })
    .fail(function () {
        console.log('Nope!');
    });

dfr.cancel(); // Now, the lines below are ignored. No console logs will appear.

dfr.resolve();
dfr.reject();