Can jQuery deferreds be cancelled?

user879121 picture user879121 · Jul 17, 2012 · Viewed 18.7k times · Source

I have a situation where I want to cancel a deferred. The deferred is associated with an ajax call.

Why I am using deferreds

I don't use the normal xhr objects returned by $.ajax. I'm using jsonp, which means I can't use HTTP status codes for error handling and have to embed them in the responses. The codes are then examined and an associated deferred object is marked as resolved or rejected accordingly. I have a custom api function that does this for me.

function api(options) {
  var url = settings('api') + options.url;
  var deferred = $.Deferred(function(){
    this.done(options.success);
    this.fail(options.error);
  });
  $.ajax({
    'url': url,
    'dataType':'jsonp',
    'data': (options.noAuth == true) ? options.data : $.extend(true, getAPICredentials(), options.data)
  }).success(function(jsonReturn){
    // Success
    if(hasStatus(jsonReturn, 'code', 200)) {
      deferred.resolveWith(this, [jsonReturn]);
    } 
    // Failure
    else {
      deferred.rejectWith(this, [jsonReturn]);
    }
  });

  return deferred;
}

Why I want to cancel the deferred

There is an input field that serves as a filter for a list and will automatically update the list half a second after typing ends. Because it is possible for two ajax calls to be outstanding at a time, I need to cancel the previous call to make sure that it doesn't return after the second and show old data.

Solutions I don't like

  • I don't want to reject the deferred because that will fire handlers attached with .fail().
  • I can't ignore it because it will automatically be marked as resolved or rejected when the ajax returns.
  • Deleting the deferred will cause an error when the ajax call returns and tries to mark the deferred as resolved or rejected.

What should I do?

Is there a way to cancel the deferred or remove any attached handlers?

Advice on how to fix my design is welcome, but preference will be given to finding a way to remove handlers or prevent them from firing.

Answer

jfriend00 picture jfriend00 · Jul 17, 2012

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.