Jaki jest najlepszy sposób ponowienia żądania AJAX w przypadku niepowodzenia przy użyciu jQuery?

107

Pseudo kod:

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

Jeszcze lepszy byłby jakiś wykładniczy wycofanie się

Tom Lehman
źródło
1
Nie jestem pewien, czy to w ogóle najlepszy sposób, więc tylko komentarz, ale jeśli wywołujesz swój Ajax z funkcji, możesz podać mu parametr tries, a w przypadku niepowodzenia wywołujesz swoją funkcję za pomocą tries+1. Zatrzymaj wykonywanie na tries==3lub dowolnej innej liczbie.
Nanne,
Dobre rozwiązanie tutaj: stackoverflow.com/questions/6298612/…
Jeremy A. West

Odpowiedzi:

238

Coś takiego:


$.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
źródło
12
Wziąłem rozwiązanie @ Sudhir i stworzyłem wtyczkę $ .retryAjax na github tutaj: github.com/mberkom/jQuery.retryAjax
Michael Berkompas
2
To nie działa dla mnie. this.tryCount w warunku jest zawsze 1.
user304602
2
@MichaelBerkompas - czy Twoja wtyczka nadal działa? Nie otrzymywało zobowiązań od 2 lat.
Hendrik,
2
czy to zadziała, jeśli inny program obsługi wywołań zwrotnych, taki jak, .successjest dołączony do wywołania funkcji, która zwraca to żądanie ajax?
ProblemsOfSumit
17
Para tryCounti retryLimitjest nadmierna. Rozważ użycie tylko jednej zmiennej:this.retryLimit--; if (this.retryLimit) { ... $.ajax(this) ... }
vladkras
15

Jednym ze sposobów jest użycie funkcji opakowującej:

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

Innym podejściem byłoby użycie retrieswłaściwości w$.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);
}

Inny sposób ( GIST ) - zastąp oryginał $.ajax(lepszy dla 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') 
});

Punkt do rozważenia jest co pewien$.ajax metoda nie była już owinięty wcześniej, aby uniknąć tego samego kodu działa dwukrotnie.


Możesz skopiować i wkleić te fragmenty (tak jak jest) do konsoli, aby je przetestować

vsync
źródło
Dzięki za scenariusz. Czy to działa z $ .ajaxSetup?
Sevban Öztürk
@ SevbanÖztürk - co masz na myśli? po prostu spróbuj :)
vsync
Dzięki za nauczenie mnie, jak budować opakowanie! To przebija stary rekurencyjny projekt funkcji, który implementowałem.
decoder7283
7

Z tym kodem poniżej odniosłem duży sukces (przykład: 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
źródło
4
Jedyną zmianą, którą chciałbym zasugerować, jest zastąpienie 'func ("' + param" '")' funkcją () {func (param)}. W ten sposób możesz bezpośrednio przekazać parametr bez konwertowania go na łańcuch iz powrotem , co może bardzo łatwo zawieść!
fabspro
@fabspro Gotowe. Dzięki!
Nabil Kadimi
7
Czy to nie jest nieskończona pętla? Biorąc pod uwagę, że pytanie ma ponowny limit i oczywiście chce obsłużyć serwer, który nigdy nie wróci ... Myślę, że to naprawdę musi tam być
PandaWood
3
jQuery.ajaxSetup () Opis: Ustaw wartości domyślne dla przyszłych żądań Ajax. Jego użycie nie jest zalecane. api.jquery.com/jQuery.ajaxSetup
blub
2

Żadna z tych odpowiedzi nie zadziała, jeśli ktoś zadzwoni .done()po wywołaniu Ajax, ponieważ nie będziesz mieć metody sukcesu, aby dołączyć do przyszłego połączenia zwrotnego. Więc jeśli ktoś to robi:

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

Wtedy mySuccessFuncnie zostanie wywołany przy ponownej próbie. Oto moje rozwiązanie, które jest mocno zapożyczone z odpowiedzi @ cjpak tutaj . W moim przypadku chcę spróbować ponownie, gdy AWS API Gateway odpowie z błędem 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);
});

Ten fragment wycofa się i spróbuje ponownie po 2 sekundach, następnie 5 sekundach, a następnie 10 sekundach, co można edytować, modyfikując stałą RETRY_WAIT.

Wsparcie AWS zasugerowało dodanie ponownej próby, ponieważ zdarza się to tylko raz na niebieskim księżycu.

Ryan Shillington
źródło
Okazało się, że jest to najbardziej przydatna ze wszystkich dotychczasowych odpowiedzi. Jednak ostatnia linia uniemożliwia kompilację w TypeScript. Myślę, że nie powinieneś zwracać czegokolwiek z tej funkcji.
Freddie
0

Oto mała wtyczka do tego:

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

Automatyczne zwiększanie limitu czasu byłoby dobrym dodatkiem do tego.

Aby używać go globalnie, po prostu utwórz własną funkcję z podpisem $ .ajax, użyj api ponawiania i zastąp wszystkie wywołania $ .ajax nową funkcją.

Możesz także bezpośrednio zamienić $ .ajax, ale nie będziesz mógł wtedy wykonywać wywołań xhr bez ponownej próby.

Oleg Isonen
źródło
0

Oto metoda, która zadziałała dla mnie w przypadku asynchronicznego ładowania bibliotek:

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

Następnie po prostu zadzwoń .load_scriptz aplikacji i zagnieżdż swoje udane wywołanie zwrotne:

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

Odpowiedź DemoUsers nie działa z Zepto, ponieważ to w funkcji błędu wskazuje na Window. (A ten sposób używania 'this' nie jest wystarczająco bezpieczny, ponieważ nie wiesz, jak implementują Ajax lub nie ma takiej potrzeby).

W przypadku Zepto może mógłbyś spróbować poniżej, do tej pory działa dobrze dla mnie:

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

Użyj konstruktora, aby upewnić się, że żądanie zostanie ponownie wprowadzone!

Xhua
źródło
0

Twój kod jest prawie pełny :)

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
źródło