jquery ajax jquery-deferred

jquery innerhtml



Vuelva a intentar una solicitud jquery ajax que tenga devoluciones de llamada asociadas a su diferido (5)

¿Te gustaría algo como esto? Solo necesita devolver su propia Deferida / Promesa para que la original no sea rechazada demasiado pronto.

Ejemplo / uso de prueba: http://jsfiddle.net/4LT2a/3/

function doSomething() { var dfr = $.Deferred(); (function makeRequest() { $.ajax({ url: "someurl", dataType: "json", success: dfr.resolve, error: function( jqXHR ) { if ( jqXHR.status === 401 ) { return makeRequest( this ); } dfr.rejectWith.apply( this, arguments ); } }); }()); return dfr.promise(); }

Estoy intentando implementar un sistema de reintentar solicitudes ajax que fallen por un motivo temporal. En mi caso, se trata de volver a intentar las solicitudes que fallaron con un código de estado 401 porque la sesión ha expirado, después de llamar a un servicio web de actualización que reactiva la sesión.

El problema es que las devoluciones de llamada "hechas" no se invocan para un reintento exitoso, a diferencia de la devolución de llamada de la opción "éxito" ajax que se llama. A continuación, hice un sencillo ejemplo:

$.ajaxSetup({statusCode: { 404: function() { this.url = ''/existent_url''; $.ajax(this); } }}); $.ajax({ url: ''/inexistent_url'', success: function() { alert(''success''); } }) .done(function() { alert(''done''); });

¿Hay alguna forma de que se necesiten devoluciones de llamada al estilo procesado para un reintento exitoso? Sé que un diferido no puede ''resolverse'' después de ser ''rechazado'', ¿es posible evitar el rechazo? ¿O tal vez copie el doneList del original diferido a un nuevo diferido? Estoy sin ideas :)

Un ejemplo más realista a continuación, donde estoy tratando de poner en cola todas las solicitudes rechazadas por 401, y las vuelvo a intentar después de una llamada exitosa a / refresh.

var refreshRequest = null, waitingRequests = null; var expiredTokenHandler = function(xhr, textStatus, errorThrown) { //only the first rejected request will fire up the /refresh call if(!refreshRequest) { waitingRequests = $.Deferred(); refreshRequest = $.ajax({ url: ''/refresh'', success: function(data) { // session refreshed, good refreshRequest = null; waitingRequests.resolve(); }, error: function(data) { // session can''t be saved waitingRequests.reject(); alert(''Your session has expired. Sorry.''); } }); } // put the current request into the waiting queue (function(request) { waitingRequests.done(function() { // retry the request $.ajax(request); }); })(this); } $.ajaxSetup({statusCode: { 401: expiredTokenHandler }});

El mecanismo funciona, las solicitudes fallidas 401 se activan por segunda vez, el problema es que sus callbacks ''hechas'' no se llaman, por lo que las aplicaciones se estancan.


Como las notas de respuesta de gnarf, las devoluciones de llamadas de éxito y error no se comportarán como se espera. Si alguien está interesado aquí, hay una versión que admite devoluciones de llamadas de success y error , así como promesas de eventos de estilo.

$.ajaxPrefilter(function (options, originalOptions, jqXHR) { // Don''t infinitely recurse originalOptions._retry = isNaN(originalOptions._retry) ? Common.auth.maxExpiredAuthorizationRetries : originalOptions._retry - 1; // set up to date authorization header with every request jqXHR.setRequestHeader("Authorization", Common.auth.getAuthorizationHeader()); // save the original error callback for later if (originalOptions.error) originalOptions._error = originalOptions.error; // overwrite *current request* error callback options.error = $.noop(); // setup our own deferred object to also support promises that are only invoked // once all of the retry attempts have been exhausted var dfd = $.Deferred(); jqXHR.done(dfd.resolve); // if the request fails, do something else yet still resolve jqXHR.fail(function () { var args = Array.prototype.slice.call(arguments); if (jqXHR.status === 401 && originalOptions._retry > 0) { // refresh the oauth credentials for the next attempt(s) // (will be stored and returned by Common.auth.getAuthorizationHeader()) Common.auth.handleUnauthorized(); // retry with our modified $.ajax(originalOptions).then(dfd.resolve, dfd.reject); } else { // add our _error callback to our promise object if (originalOptions._error) dfd.fail(originalOptions._error); dfd.rejectWith(jqXHR, args); } }); // NOW override the jqXHR''s promise functions with our deferred return dfd.promise(jqXHR); });


Esta es una gran pregunta que acabo de enfrentar también.

Me atemorizó la respuesta aceptada (de @gnarf), así que descubrí una manera que entendí más fácil:

var retryLimit = 3; var tryCount = 0; callAjax(payload); function callAjax(payload) { tryCount++; var newSaveRequest = $.ajax({ url: ''/survey/save'', type: ''POST'', data: payload, headers: { ''X-CSRF-TOKEN'': $(''meta[name="csrf-token"]'').attr(''content'') }, error: function (xhr, textStatus, errorThrown) { if (textStatus !== ''abort'') { console.log(''Error on '' + thisAnswerRequestNum, xhr, textStatus, errorThrown); if (tryCount <= retryLimit) { sleep(2000).then(function () { if ($.inArray(thisAnswerRequestNum, abortedRequestIds) === -1) { console.log(''Trying again '' + thisAnswerRequestNum); callAjax(payload);//try again } }); return; } return; } } }); newSaveRequest.then(function (data) { var newData = self.getDiffFromObjects(recentSurveyData, data); console.log("Answer was recorded " + thisAnswerRequestNum, newData);//, data, JSON.stringify(data) recentSurveyData = data; }); self.previousQuizAnswerAjax = newSaveRequest; self.previousQuizAnswerIter = thisAnswerRequestNum; } function sleep(milliseconds) { return new Promise((resolve) => setTimeout(resolve, milliseconds)); }

Básicamente, envolví toda la llamada de Ajax y sus devoluciones de llamada en una función que se puede llamar de forma recursiva.


Puede usar jQuery.ajaxPrefilter para ajustar el jqXHR en otro objeto diferido .

Hice un ejemplo en jsFiddle que lo muestra funcionando, e intenté adaptar parte de tu código para manejar el 401 en esta versión:

$.ajaxPrefilter(function(opts, originalOpts, jqXHR) { // you could pass this option in on a "retry" so that it doesn''t // get all recursive on you. if (opts.refreshRequest) { return; } // our own deferred object to handle done/fail callbacks var dfd = $.Deferred(); // if the request works, return normally jqXHR.done(dfd.resolve); // if the request fails, do something else // yet still resolve jqXHR.fail(function() { var args = Array.prototype.slice.call(arguments); if (jqXHR.status === 401) { $.ajax({ url: ''/refresh'', refreshRequest: true, error: function() { // session can''t be saved alert(''Your session has expired. Sorry.''); // reject with the original 401 data dfd.rejectWith(jqXHR, args); }, success: function() { // retry with a copied originalOpts with refreshRequest. var newOpts = $.extend({}, originalOpts, { refreshRequest: true }); // pass this one on to our deferred pass or fail. $.ajax(newOpts).then(dfd.resolve, dfd.reject); } }); } else { dfd.rejectWith(jqXHR, args); } }); // NOW override the jqXHR''s promise functions with our deferred return dfd.promise(jqXHR); });

Esto funciona porque deferred.promise(object) realmente sobrescribirá todos los "métodos de promesa" en el jqXHR.

NOTA: Para cualquier otra persona que encuentre esto, si está adjuntando devoluciones de llamada con success: y error: en las opciones de ajax, este fragmento no funcionará de la manera esperada. Supone que las únicas retrollamadas son las que se adjuntan usando los .done(callback) y .fail(callback) del jqXHR.


Creé un plugin jQuery para este caso de uso. Cubre la lógica descrita en la respuesta de gnarf en un complemento y, además, le permite especificar un tiempo de espera para esperar antes de volver a intentar la llamada ajax. Por ejemplo.

//this will try the ajax call three times in total //if there is no error, the success callbacks will be fired immediately //if there is an error after three attempts, the error callback will be called $.ajax(options).retry({times:3}).then(function(){ alert("success!"); }); //this has the same sematics as above, except will //wait 3 seconds between attempts $.ajax(options).retry({times:3, timeout:3000}).retry(3).then(function(){ alert("success!"); });