jQuery jqXHR - cancel chained calls, trigger error chain

m0sa picture m0sa · Feb 25, 2011 · Viewed 14.8k times · Source

I am creating a ajax utility for interfacing with my server methods. I would like to leverage jQuery 1.5+ deferred methods from the object returned from the jQuery.ajax() call. The situation is following.

  1. The serverside method always returns a JSON object:

    { success: true|false, data: ... }

  2. The client-side utility initiates the ajax call like this

    var jqxhr = $.ajax({ ... });

  3. And the problem area:

    jqxhr.success(function(data, textStatus, xhr) {
         if(!data || !data.success) { 
             ???? // abort processing, trigger error
         }
    });
    return jqxhr; // return to caller so he can attach his own handlers
    

So the question is how to cancel invocation of all the callers appended success callbacks an trigger his error handler in the place mentioned with ???? ?

The documentation says the deferred function invocation lists are FIFO, so my success handler is definitely the first one.

Answer

rsp picture rsp · Feb 25, 2011

(UPDATE: Please note that currently jQuery Promises are not compatible with the Promises/A+ specification - more info in this answer.)

In your function where you create the AJAX request you can also create a deferred object and return a promise to the caller after binding its resolve and reject functions to the appropriate callbacks of the $.ajax request with some custom data verification, like this:

function makerequest() {

    var deferred = $.Deferred();
    var promise = deferred.promise();

    var jqxhr = $.ajax({
        // ...
    });

    jqxhr.success(function(data, status, xhr) {
        if (!data || !data.success) {
            deferred.reject(jqxhr, 'error');
        } else {
            deferred.resolve(data, status, xhr);
        }
    });

    jqxhr.error(function(jqXHR, status, error) {
        deferred.reject(jqXHR, status, error);
    });

    return promise;
}

Now anyone will be able to use it like any promise like this to your function:

var request = makerequest();

request.done(successCallback);
request.fail(errorCallback);

Or even just:

makerequest().then(successCallback, errorCallback);

If you also add this:

    promise.success = promise.done;
    promise.error = promise.fail;

then your caller will have (maybe more familiar) interface of .success() and .error() like with pure $.ajax() calls:

var request = makerequest();

request.success(successCallback);
request.error(errorCallback);

(The implementation of .complete() is left as an exercise for the reader.)

See this demos:

Here's another example pulled directly from a working project:

function ajax(url, data) {
    var self = this;
    var deferred = $.Deferred();
    var promise = deferred.promise();

    var jqxhr = $.ajax({
        url: url,
        data: JSON.stringify(data),
        contentType: "application/json; charset=utf-8",
        dataType: 'json',
        type: 'POST'
    }).done(function (msg, status, xhr) {
        if (!msg || msg.Error) {
            self.doError(msg.Error);
            deferred.reject(jqxhr, 'error');
        } else {
            deferred.resolve(msg, status, xhr);
        }
    });

    return promise;
}