Qual é a melhor maneira de tentar novamente uma solicitação AJAX em caso de falha usando jQuery?

107

Pseudo-código:

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

Melhor ainda seria algum tipo de recuo exponencial

Tom Lehman
fonte
1
Não tenho certeza se essa é a melhor maneira, então apenas um comentário, mas se você chamar seu ajax de uma função, você pode dar a ele um parâmetro triese, em caso de falha, chamar sua função com tries+1. Pare a execução em tries==3ou qualquer outro número.
Nanne,
Boa solução aqui: stackoverflow.com/questions/6298612/…
Jeremy A. West

Respostas:

238

Algo assim:


$.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
fonte
12
Peguei a solução de @Sudhir e criei um plugin $ .retryAjax no github aqui: github.com/mberkom/jQuery.retryAjax
Michael Berkompas
2
Isso não está funcionando para mim. this.tryCount na condicional é sempre 1.
user304602
2
@MichaelBerkompas - seu plugin ainda funciona? Não recebe commits há 2 anos.
Hendrik
2
isso funcionará se outro manipulador de retorno de chamada como .successestiver vinculado à chamada da função que retorna esta solicitação ajax?
ProblemsOfSumit
17
Par de tryCounte retryLimité excessivo. Considere o uso de apenas uma variável:this.retryLimit--; if (this.retryLimit) { ... $.ajax(this) ... }
vladkras
15

Uma abordagem é usar uma função de wrapper:

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

Outra abordagem seria usar uma retriespropriedade no$.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);
}

Outra forma ( GIST ) - sobrescrever original $.ajax(melhor 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') 
});

Um ponto a ser considerado é ter certeza de que o $.ajaxmétodo já não foi agrupado anteriormente, para evitar que o mesmo código seja executado duas vezes.


Você pode copiar e colar esses snippets (no estado em que se encontram) no console para testá-los

vsync
fonte
Obrigado pelo script. Funciona com $ .ajaxSetup?
Sevban Öztürk
@ SevbanÖztürk - o que você quer dizer? tente :)
vsync
Obrigado por me ensinar como estruturar um invólucro! Isso supera o antigo design de função recursiva que costumava implementar.
decoder7283 01 de
7

Tive muito sucesso com este código abaixo (exemplo: 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
fonte
4
A única alteração que sugiro é substituir 'func ("' + param" '")' por function () {func (param)}. Dessa forma, você pode passar o parâmetro diretamente sem convertê-lo em uma string e de volta , que pode falhar muito facilmente!
fabspro
@fabspro Done. Obrigado!
Nabil Kadimi
7
Não é um loop infinito? Dada a questão tem um retryLimit e está obviamente querendo atender ao servidor nunca mais voltar ... Eu acho que isso realmente tem que estar lá
PandaWood
3
jQuery.ajaxSetup () Descrição: Defina os valores padrão para futuras solicitações Ajax. Seu uso não é recomendado. api.jquery.com/jQuery.ajaxSetup
blub de
2

Nenhuma dessas respostas funciona se alguém ligar .done()após sua chamada de ajax, porque você não terá o método de sucesso para anexar à chamada de retorno futura. Então, se alguém fizer isso:

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

Então mySuccessFuncnão será chamado na nova tentativa. Aqui está minha solução, que foi muito emprestada da resposta de @cjpak aqui . No meu caso, quero tentar novamente quando o API Gateway da AWS responder com o erro 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 snippet recuará e tentará novamente após 2 segundos, depois 5 segundos e depois 10 segundos, que você pode editar modificando a constante RETRY_WAIT.

O suporte da AWS sugeriu que adicionássemos uma nova tentativa, já que isso acontece para nós apenas uma vez em uma lua azul.

Ryan Shillington
fonte
Achei esta a mais útil de todas as respostas até agora. No entanto, a última linha impede a compilação em TypeScript. Não acho que você deva retornar nada desta função.
Freddie
0

Aqui está um pequeno plugin para isso:

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

O incremento automático do tempo limite seria uma boa adição a ele.

Para usá-lo globalmente, basta criar sua própria função com a assinatura $ .ajax, usar a api de nova tentativa e substituir todas as suas chamadas $ .ajax por sua nova função.

Além disso, você pode substituir $ .ajax diretamente, mas não poderá fazer chamadas xhr sem tentar novamente.

Oleg Isonen
fonte
0

Este é o método que funcionou para mim para carregamento assíncrono 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
    });
  }
}

Em seguida, basta chamar .load_scriptde seu aplicativo e aninhar seu retorno de chamada de sucesso:

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

A resposta do DemoUsers não funciona com o Zepto, já que esta na função de erro está apontando para a janela. (E essa forma de usar 'this' não é segura o suficiente, pois você não sabe como eles implementam ajax ou não precisa fazer isso.)

Para Zepto, talvez você possa tentar abaixo, até agora funciona bem para mim:

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 o construtor para garantir que a solicitação seja reentrante!

Xhua
fonte
0

Seu código está quase cheio :)

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
fonte