¿Cuál es la mejor manera de reintentar una solicitud AJAX en caso de falla usando jQuery?

107

Pseudo código:

$(document).ajaxError(function(e, xhr, options, error) {
  xhr.retry()
})

Incluso mejor sería una especie de retroceso exponencial

Tom Lehman
fuente
1
No estoy seguro de si esta es la mejor manera, así que solo un comentario, pero si llamas a tu ajax desde una función, puedes darle un parámetro triesy, en caso de falla, llamas a tu función con tries+1. Detenga la ejecución en tries==3o cualquier otro número.
Nanne
Buena solución aquí: stackoverflow.com/questions/6298612/…
Jeremy A. West

Respuestas:

238

Algo como esto:


$.ajax({
    url : 'someurl',
    type : 'POST',
    data :  ....,   
    tryCount : 0,
    retryLimit : 3,
    success : function(json) {
        //do something
    },
    error : function(xhr, textStatus, errorThrown ) {
        if (textStatus == 'timeout') {
            this.tryCount++;
            if (this.tryCount <= this.retryLimit) {
                //try again
                $.ajax(this);
                return;
            }            
            return;
        }
        if (xhr.status == 500) {
            //handle error
        } else {
            //handle error
        }
    }
});
Sudhir Bastakoti
fuente
12
Tomé la solución de @ Sudhir y creé un complemento $ .retryAjax en github aquí: github.com/mberkom/jQuery.retryAjax
Michael Berkompas
2
Esto no me está funcionando. this.tryCount en el condicional es siempre 1.
user304602
2
@MichaelBerkompas: ¿tu complemento todavía funciona? No ha recibido compromisos en 2 años.
Hendrik
2
¿ .successFuncionará esto si se adjunta otro controlador de devolución de llamada como para llamar a la función que devuelve esta solicitud ajax?
ProblemsOfSumit
17
Par de tryCounty retryLimites excesivo. Considere usar solo 1 variable:this.retryLimit--; if (this.retryLimit) { ... $.ajax(this) ... }
vladkras
15

Un enfoque es utilizar una función contenedora:

(function runAjax(retries, delay){
  delay = delay || 1000;
  $.ajax({
    type        : 'GET',
    url         : '',
    dataType    : 'json',
    contentType : 'application/json'
  })
  .fail(function(){
    console.log(retries); // prrint retry count
    retries > 0 && setTimeout(function(){
        runAjax(--retries);
    },delay);
  })
})(3, 100);

Otro enfoque sería utilizar una retriespropiedad en el$.ajax

// define ajax settings
var ajaxSettings = {
  type        : 'GET',
  url         : '',
  dataType    : 'json',
  contentType : 'application/json',
  retries     : 3  //                 <-----------------------
};

// run initial ajax
$.ajax(ajaxSettings).fail(onFail)

// on fail, retry by creating a new Ajax deferred
function onFail(){
  if( ajaxSettings.retries-- > 0 )
    setTimeout(function(){
        $.ajax(ajaxSettings).fail(onFail);
    }, 1000);
}

Otra forma ( GIST ): anular original $.ajax(mejor para DRY)

// enhance the original "$.ajax" with a retry mechanism 
$.ajax = (($oldAjax) => {
  // on fail, retry by creating a new Ajax deferred
  function check(a,b,c){
    var shouldRetry = b != 'success' && b != 'parsererror';
    if( shouldRetry && --this.retries > 0 )
      setTimeout(() => { $.ajax(this) }, this.retryInterval || 100);
  }

  return settings => $oldAjax(settings).always(check)
})($.ajax);



// now we can use the "retries" property if we need to retry on fail
$.ajax({
    type          : 'GET',
    url           : 'http://www.whatever123.gov',
    timeout       : 2000,
    retries       : 3,     //       <-------- Optional
    retryInterval : 2000   //       <-------- Optional
})
// Problem: "fail" will only be called once, and not for each retry
.fail(()=>{
  console.log('failed') 
});

Un punto a considerar es asegurarse de que el $.ajaxmétodo no haya sido empaquetado previamente para evitar que el mismo código se ejecute dos veces.


Puede copiar y pegar estos fragmentos (tal cual) en la consola para probarlos

vsync
fuente
Gracias por el guion. ¿Funciona con $ .ajaxSetup?
Sevban Öztürk
@ SevbanÖztürk - ¿qué quieres decir? solo intenta :)
vsync
¡Gracias por enseñarme a estructurar una envoltura! Esto supera al antiguo diseño de función recursiva que solía implementar.
decoder7283
7

He tenido mucho éxito con este código a continuación (ejemplo: http://jsfiddle.net/uZSFK/ )

$.ajaxSetup({
    timeout: 3000, 
    retryAfter:7000
});

function func( param ){
    $.ajax( 'http://www.example.com/' )
        .success( function() {
            console.log( 'Ajax request worked' );
        })
        .error(function() {
            console.log( 'Ajax request failed...' );
            setTimeout ( function(){ func( param ) }, $.ajaxSetup().retryAfter );
        });
}
Nabil Kadimi
fuente
4
El único cambio que sugeriría es reemplazar 'func ("' + param" '")' con function () {func (param)}. De esa manera, puede pasar directamente el parámetro sin convertirlo en una cadena y viceversa , que puede fallar muy fácilmente!
fabspro
@fabspro Hecho. ¡Gracias!
Nabil Kadimi
7
¿No es este un bucle sin fin? Dada la pregunta tiene un retryLimit y obviamente quiere atender a que el servidor nunca regrese ... Creo que esto realmente tiene que estar ahí
PandaWood
3
jQuery.ajaxSetup () Descripción: establece valores predeterminados para futuras solicitudes de Ajax. No se recomienda su uso. api.jquery.com/jQuery.ajaxSetup
blub
2

Ninguna de estas respuestas funciona si alguien llama .done()después de su llamada ajax porque no tendrá el método de éxito para adjuntar a la futura devolución de llamada. Entonces, si alguien hace esto:

$.ajax({...someoptions...}).done(mySuccessFunc);

Entonces mySuccessFuncno se llamará en el reintento. Aquí está mi solución, que está muy tomada de la respuesta de @ cjpak aquí . En mi caso, quiero volver a intentarlo cuando API Gateway de AWS responda con el error 502.

const RETRY_WAIT = [10 * 1000, 5 * 1000, 2 * 1000];

// This is what tells JQuery to retry $.ajax requests
// Ideas for this borrowed from https://stackoverflow.com/a/12446363/491553
$.ajaxPrefilter(function(opts, originalOpts, jqXHR) {
  if(opts.retryCount === undefined) {
    opts.retryCount = 3;
  }

  // Our own deferred object to handle done/fail callbacks
  let dfd = $.Deferred();

  // If the request works, return normally
  jqXHR.done(dfd.resolve);

  // If the request fails, retry a few times, yet still resolve
  jqXHR.fail((xhr, textStatus, errorThrown) => {
    console.log("Caught error: " + JSON.stringify(xhr) + ", textStatus: " + textStatus + ", errorThrown: " + errorThrown);
    if (xhr && xhr.readyState === 0 && xhr.status === 0 && xhr.statusText === "error") {
      // API Gateway gave up.  Let's retry.
      if (opts.retryCount-- > 0) {
        let retryWait = RETRY_WAIT[opts.retryCount];
        console.log("Retrying after waiting " + retryWait + " ms...");
        setTimeout(() => {
          // Retry with a copied originalOpts with retryCount.
          let newOpts = $.extend({}, originalOpts, {
            retryCount: opts.retryCount
          });
          $.ajax(newOpts).done(dfd.resolve);
        }, retryWait);
      } else {
        alert("Cannot reach the server.  Please check your internet connection and then try again.");
      }
    } else {
      defaultFailFunction(xhr, textStatus, errorThrown); // or you could call dfd.reject if your users call $.ajax().fail()
    }
  });

  // NOW override the jqXHR's promise functions with our deferred
  return dfd.promise(jqXHR);
});

Este fragmento retrocederá y volverá a intentarlo después de 2 segundos, luego 5 segundos, luego 10 segundos, que puede editar modificando la constante RETRY_WAIT.

El soporte de AWS sugirió que agreguemos un reintento, ya que nos sucede solo una vez en una luna azul.

Ryan Shillington
fuente
Encontré que esta es la más útil de todas las respuestas hasta ahora. Sin embargo, la última línea evita la compilación en TypeScript. No creo que debas devolver nada de esta función.
Freddie
0

Aquí hay un pequeño complemento para esto:

https://github.com/execjosh/jquery-ajax-retry

Incrementar automáticamente el tiempo de espera sería una buena adición.

Para usarlo globalmente, simplemente cree su propia función con la firma $ .ajax, use la API de reintento y reemplace todas sus llamadas a $ .ajax por su nueva función.

También puede reemplazar directamente $ .ajax, pero no podrá realizar llamadas xhr sin volver a intentarlo.

Oleg Isonen
fuente
0

Aquí está el método que funcionó para mí para la carga asincrónica de bibliotecas:

var jqOnError = function(xhr, textStatus, errorThrown ) {
    if (typeof this.tryCount !== "number") {
      this.tryCount = 1;
    }
    if (textStatus === 'timeout') {
      if (this.tryCount < 3) {  /* hardcoded number */
        this.tryCount++;
        //try again
        $.ajax(this);
        return;
      }
      return;
    }
    if (xhr.status === 500) {
        //handle error
    } else {
        //handle error
    }
};

jQuery.loadScript = function (name, url, callback) {
  if(jQuery[name]){
    callback;
  } else {
    jQuery.ajax({
      name: name,
      url: url,
      dataType: 'script',
      success: callback,
      async: true,
      timeout: 5000, /* hardcoded number (5 sec) */
      error : jqOnError
    });
  }
}

Luego, simplemente llame .load_scriptdesde su aplicación y anide su devolución de llamada exitosa:

$.loadScript('maps', '//maps.google.com/maps/api/js?v=3.23&libraries=geometry&libraries=places&language=&hl=&region=', function(){
    initialize_map();
    loadListeners();
});
Abram
fuente
0

La respuesta de DemoUsers no funciona con Zepto, ya que esto en la función de error apunta a Window. (Y esa forma de usar 'esto' no es lo suficientemente segura ya que no sabe cómo implementan ajax o no es necesario).

Para Zepto, tal vez puedas probar a continuación, hasta ahora funciona bien para mí:

var AjaxRetry = function(retryLimit) {
  this.retryLimit = typeof retryLimit === 'number' ? retryLimit : 0;
  this.tryCount = 0;
  this.params = null;
};
AjaxRetry.prototype.request = function(params, errorCallback) {
  this.tryCount = 0;
  var self = this;
  params.error = function(xhr, textStatus, error) {
    if (textStatus === 'timeout') {
      self.tryCount ++;
      if (self.tryCount <= self.retryLimit) {
        $.ajax(self.params)      
        return;
      }
    }
    errorCallback && errorCallback(xhr, textStatus, error);
  };
  this.params = params;
  $.ajax(this.params);
};
//send an ajax request
new AjaxRetry(2).request(params, function(){});

Use el constructor para asegurarse de que la solicitud sea reentrante.

Xhua
fuente
0

Tu código está casi lleno :)

const counter = 0;
$(document).ajaxSuccess(function ( event, xhr, settings ) {
    counter = 0;
}).ajaxError(function ( event, jqxhr, settings, thrownError ) {
    if (counter === 0 /*any thing else you want to check ie && jqxhr.status === 401*/) {
        ++counter;
        $.ajax(settings);
    }
});
Andriy
fuente