ejemplo javascript jquery ajax jquery-deferred cancellation

getelementbyid javascript ejemplo



¿Se pueden cancelar jQuery diferidos? (4)

Al mirar el código y el documento de jQuery, no veo ninguna forma de cancelar un jQuery diferido.

En su lugar, probablemente necesite una forma en su resolveWith manejador para saber que una llamada ajax posterior ya ha sido activada y esta llamada ajax debe ignorar su resultado. Podrías hacer eso con un contador de incremento global. Al comienzo de su llamada ajax, incrementa el contador y luego toma el valor en una variable local o lo pone como una propiedad en el objeto ajax. En su resolveWith controlador, compruebe si el contador todavía tiene el mismo valor que cuando comenzó su llamada ajax. Si no, ignoras el resultado. Si lo hace, no se han lanzado nuevas llamadas ajax para que pueda procesar el resultado.

Alternativamente, podría negarse a realizar una nueva llamada ajax mientras ya está en vuelo, de modo que nunca haya tenido más de uno en vuelo a la vez. Cuando el finalice, podrías simplemente usar ese resultado o disparar el siguiente si así lo deseas.

Tengo una situación en la que quiero cancelar un aplazado. El diferido se asocia con una llamada ajax.

Por qué estoy usando diferidos

No uso los objetos xhr normales devueltos por $ .ajax. Estoy usando jsonp, lo que significa que no puedo usar códigos de estado HTTP para el manejo de errores y tengo que incrustarlos en las respuestas. Los códigos son luego examinados y un objeto diferido asociado se marca como resuelto o rechazado en consecuencia. Tengo una función api personalizada que hace esto por mí.

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

Por qué quiero cancelar el diferido

Hay un campo de entrada que sirve como filtro para una lista y automáticamente actualizará la lista medio segundo después de que termine de escribir. Debido a que es posible que dos llamadas ajax sean sobresalientes a la vez, necesito cancelar la llamada anterior para asegurarme de que no vuelva después del segundo y mostrar los datos anteriores.

Soluciones que no me gustan

  • No quiero rechazar el diferido porque eso despedirá a los controladores adjuntos con .fail() .
  • No puedo ignorarlo porque se marcará automáticamente como resuelto o rechazado cuando vuelva el ajax.
  • Eliminar el diferido causará un error cuando la llamada ajax regrese e intente marcar el diferido como resuelto o rechazado.

¿Que debería hacer?

¿Hay alguna manera de cancelar el diferido o eliminar los controladores adjuntos?

Es bienvenido el asesoramiento sobre cómo arreglar mi diseño, pero se dará preferencia a encontrar una forma de eliminar los controladores o evitar que se activen.


Creé un complemento que agrega la capacidad de cancelar objetos diferidos y solicitudes de ajax.

En resumen, una vez que se ha cancelado un objeto diferido, las resoluciones / rechazos se ignoran por completo, y el state se "cancela".

De acuerdo con jQuery.com, "una vez que el objeto ha ingresado al estado resuelto o rechazado, permanece en ese estado". Por lo tanto, los intentos de cancelar se ignoran una vez que un objeto diferido se resuelve o rechaza.

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

Una vez agregado, puede cancelar una llamada 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.

..o un simple objeto diferido:

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


Justin: parece que ya estás muy cerca de lo que quieres. Ya estás usando dos diferidos (inner-> the ajax y outer -> $. Diferido ()). A continuación, usa el diferido interno para decidir cómo resolver el diferimiento externo en función de algunas condiciones.

Bueno, simplemente no resuelvas el diferido externo cuando no quieras (quizás tengas una variable booleana que sirva como puerta de alternar para permitir que la dfd interna se resuelva / rechace). No pasará nada malo: los manejadores que hayas conectado a esta función completa no se dispararán. Ejemplo en tu función de éxito interno:

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

Alguna otra lógica en la aplicación decidirá cuándo gateOpen vuelve a ser verdadero (algún tipo de _.throttle () o _.debounce () timeout, interacción del usuario, lo que quieras). Si deseas rastrear o cancelar otras solicitudes en el resto de esa función, podrías hacer eso también. Pero lo básico es que no tiene que resolver O rechazar ese diferido externo. Y eso es lo mismo que cancelarlo, incluso si no cancela / aborta el interno.


Si bien no puede "cancelar" un diferido como desee, puede crear un cierre simple para realizar un seguimiento de la última llamada ajax a través de $ .ajax devolviendo un objeto jqXHR. Al hacer esto, simplemente puede cancelar () la llamada cuando ingrese un nuevo jqXHR para reproducir si el último no se terminó. En el caso de su código, rechazará el jqXHR y dejará abierto el diferido para que se elimine como lo deseaba inicialmente.

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

He publicado esto en jsfiddle . Si deseas probarlo. Set timeout se usa en combinación con jsfiddles delayer para simular que una llamada se interrumpe. Necesitará un navegador habilitado para consola para ver los registros.

En una nota al margen, cambie cualquier método .success (), .error () y complete () a métodos aplazados done (), fail () y always (). A través de jquery/ajax

Aviso de desaprobación: las devoluciones de llamada jqXHR.success (), jqXHR.error () y jqXHR.complete () quedarán obsoletas en jQuery 1.8. Para preparar su código para su posible eliminación, utilice jqXHR.done (), jqXHR.fail () y jqXHR.always () en su lugar como más nuevo