Les retards de jQuery peuvent-ils être annulés?

J'ai une situation où je souhaite annuler un report. Le report est associé à un appel ajax.

Pourquoi j'utilise les différés

Je n'utilise pas les objets xhr normaux retournés par $ .ajax. J'utilise jsonp, ce qui signifie que je ne peux pas utiliser les codes d'état HTTP pour la gestion des erreurs et les intégrer dans les réponses. Les codes sont ensuite examinés et un objet différé associé est marqué comme résolu ou rejeté en conséquence. J'ai une fonction api personnalisée qui fait cela pour moi.

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; } 

Pourquoi je veux annuler le report

Il existe un champ de saisie qui sert de filtre pour une liste et met automatiquement à jour la liste une demi-seconde après la fin de la saisie. Parce qu'il est possible que deux appels ajax soient exceptionnels à la fois, j'ai besoin d'annuler l'appel précédent pour m'assurer qu'il ne retourne pas après le second et affiche les anciennes données.

Solutions que je n'aime pas

  • Je ne veux pas rejeter le report parce que les gestionnaires de tir seront attachés avec .fail() .
  • Je ne peux pas l'ignorer car il sera automatiquement marqué comme résolu ou rejeté lorsque l'ajax revient.
  • La suppression du report entraînera une erreur lorsque l'appel ajax retournera et tentera de marquer le différé comme résolu ou rejeté.

Que devrais-je faire?

Existe-t-il un moyen d'annuler le report ou de supprimer les gestionnaires attachés?

Des conseils sur la façon de réparer ma conception sont les bienvenus, mais il sera préférable de trouver un moyen de supprimer les gestionnaires ou de les empêcher de tirer.

En regardant dans le document jQuery et le code, je ne vois aucun moyen d'annuler un jQuery différé.

Au lieu de cela, vous avez probablement besoin d'un moyen de resolveWith votre resolveWith gestionnaire pour savoir qu'un appel ajax ultérieur a déjà été déclenché et cet appel ajax doit ignorer son résultat. Vous pouvez le faire avec un compteur globalement incrémental. Au début de votre appel ajax, vous augmentez le compteur, puis vous saisissez la valeur dans une variable locale ou la mettez comme une propriété sur l'objet ajax. Dans votre resolveWith gestionnaire, vous vérifiez si le compteur a toujours la même valeur que lorsque votre appel ajax a commencé. Sinon, vous ignorez le résultat. Si c'est le cas, aucun nouvel appel ajax n'a été lancé afin que vous puissiez traiter le résultat.

Alternativement, vous pouvez refuser de déclencher un nouvel appel ajax alors que vous êtes déjà en avion, donc vous n'avez jamais eu plus d'un vol en même temps. Lorsque l'on finit, vous pouvez soit utiliser ce résultat, soit tirer le prochain si vous le souhaitez.

Alors que vous ne pouvez pas "annuler" un différé comme vous le souhaitez, vous pouvez créer une fermeture simple pour suivre le dernier appel ajax par $ .ajax en retournant un objet jqXHR. En faisant cela, vous pouvez simplement annuler () l'appel lorsqu'un nouveau jqXHR entre en jeu si le dernier n'était pas terminé. Dans le cas de votre code, il rejette le jqXHR et laissez l'ouverture différée pour être supprimé comme vous l'avez souhaité.

 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(); }; })(); 

J'ai posté ceci sur jsfiddle . Si vous souhaitez le tester. Définir le délai d'attente est utilisé en combinaison avec jsfiddles delayer pour simuler un appel interupté. Vous aurez besoin d'un navigateur compatible avec la console pour voir les journaux.

Sur une touche latérale, modifiez toutes les méthodes .success (), .error () et complete () sur les méthodes reportées effectuées (), fail () et always (). Via jquery / ajax

Avis de dépréciation: les retours de jqXHR.success (), jqXHR.error () et jqXHR.complete () seront obsolètes dans jQuery 1.8. Pour préparer votre code pour leur éventuel retrait, utilisez jqXHR.done (), jqXHR.fail () et jqXHR.always () plutôt que plus récents

JustinY: il semble que vous soyez vraiment proche de ce que vous voulez. Vous utilisez déjà deux reports (intérieur-> l'ajax et externe -> $ .Deferred ()). Vous utilisez ensuite le différé interne pour décider de la façon de résoudre le report externe en fonction de certaines conditions.

Eh bien, alors ne résolvez pas le différé externe du tout lorsque vous ne voulez pas (peut-être que vous avez une variable booléenne qui sert de porte-bascule pour permettre au dfd interne de résoudre / rejeter du tout). Rien de mal ne se produira: n'importe quel gestionnaire que vous avez attaché à cette fonction entière ne déclenchera pas. Exemple dans votre fonction de réussite interne:

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

Une autre logique dans l'application décidera quand le gateOpen est rétabli sur true (une sorte de _.throttle () ou _.debounce () timeout, user interaction, what you want). Si vous souhaitez suivre ou annuler d'autres demandes Dans l'autre de cette fonction, vous pourriez le faire aussi. Mais le fondamental est que vous n'avez pas à résoudre OU à rejeter cette exclusion différée. Et c'est la même chose que de l'annuler, même si vous n'annulez pas ou n'abandonne pas l'intérieur.

J'ai créé une caisse qui ajoute sans cesse la possibilité d'annuler les objets différés et les requêtes ajax.

En bref, une fois qu'un objet différé a été annulé, les résolutions / refus sont complètement ignorés et l' state devient "annulé".

Selon jQuery.com, "Une fois que l'objet est entré dans l'état résolu ou rejeté, il reste dans cet état". Par conséquent, les tentatives d'annulation sont ignorées une fois qu'un objet reporté est résolu ou rejeté.

 (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; }; }); 

Une fois ajouté, vous pouvez annuler un appel ajax:

 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. 

… ou un simple objet différé:

 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();