Wie kann eine AJAX-Anforderung bei einem Fehler mit jQuery am besten wiederholt werden?

107

Pseudocode:

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

Noch besser wäre eine Art exponentieller Rückzieher

Tom Lehman
quelle
1
Ich bin mir nicht sicher, ob dies überhaupt der beste Weg ist, also nur ein Kommentar, aber wenn Sie Ihren Ajax von einer Funktion aus aufrufen, können Sie ihm einen Parameter geben tries, und wenn dies fehlschlägt, rufen Sie Ihre Funktion mit auf tries+1. Beenden Sie die Ausführung auf tries==3oder einer anderen Nummer.
Nanne
Gute
Jeremy A. West

Antworten:

238

Etwas wie das:


$.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
quelle
12
Ich habe @ Sudhirs Lösung genommen und hier ein $ .retryAjax-Plugin auf github erstellt: github.com/mberkom/jQuery.retryAjax
Michael Berkompas
2
Das funktioniert bei mir nicht. this.tryCount in der Bedingung ist immer 1.
user304602
2
@ MichaelBerkompas - funktioniert dein Plugin noch? Es hat seit 2 Jahren keine Zusagen mehr erhalten.
Hendrik
2
Funktioniert dies, wenn ein anderer Callback-Handler wie .successder Aufruf der Funktion, die diese Ajax-Anforderung zurückgibt, angehängt ist?
Probleme von
17
Paar tryCountund retryLimitist übertrieben. Verwenden Sie nur 1 Variable:this.retryLimit--; if (this.retryLimit) { ... $.ajax(this) ... }
Vladkras
15

Ein Ansatz ist die Verwendung einer Wrapper-Funktion:

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

Ein anderer Ansatz wäre die Verwendung einer retriesEigenschaft auf dem$.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);
}

Ein anderer Weg ( GIST ) - Original überschreiben $.ajax(besser für 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') 
});

Ein zu berücksichtigender Punkt ist, sicherzustellen , dass die $.ajaxMethode noch nicht zuvor umbrochen wurde, um zu vermeiden, dass derselbe Code zweimal ausgeführt wird.


Sie können diese Snippets (wie sie sind) kopieren und in die Konsole einfügen, um sie zu testen

vsync
quelle
Danke für das Skript. Funktioniert es mit $ .ajaxSetup?
Sevban Öztürk
@ SevbanÖztürk - was meinst du? versuchen Sie es einfach :)
vsync
Vielen Dank, dass Sie mir beigebracht haben, wie man einen Wrapper strukturiert! Dies übertrifft das alte rekursive Funktionsdesign, das ich zur Implementierung verwendet habe.
decoder7283
7

Ich habe mit diesem Code unten viel Erfolg gehabt (Beispiel: 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
quelle
4
Die einzige Änderung, die ich vorschlagen würde, besteht darin, 'func ("' + param" '")' durch function () {func (param)} zu ersetzen. Auf diese Weise können Sie den Parameter direkt weitergeben, ohne ihn in einen String und zurück zu konvertieren , was sehr leicht scheitern kann!
fabspro
@fabspro Fertig. Vielen Dank!
Nabil Kadimi
7
Ist das nicht eine Endlosschleife? Angesichts der Frage hat ein retryLimit und möchte offensichtlich für den Server sorgen, der nie zurückkommt ... Ich denke, das muss wirklich da sein
PandaWood
3
jQuery.ajaxSetup () Beschreibung: Legen Sie Standardwerte für zukünftige Ajax-Anforderungen fest. Die Verwendung wird nicht empfohlen. api.jquery.com/jQuery.ajaxSetup
Blub
2

Keine dieser Antworten funktioniert, wenn jemand .done()nach seinem Ajax-Anruf anruft , da Sie nicht über die Erfolgsmethode verfügen, die Sie an den zukünftigen Rückruf anhängen können. Also, wenn jemand dies tut:

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

Dann mySuccessFuncwird der Wiederholungsversuch nicht aufgerufen. Hier ist meine Lösung, die stark von der Antwort von @ cjpak hier entlehnt ist . In meinem Fall möchte ich es erneut versuchen, wenn das API-Gateway von AWS mit einem 502-Fehler antwortet.

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

Dieses Snippet wird nach 2 Sekunden, dann nach 5 Sekunden und nach 10 Sekunden zurückgesetzt und erneut versucht. Sie können es bearbeiten, indem Sie die Konstante RETRY_WAIT ändern.

Der AWS-Support schlug vor, einen erneuten Versuch hinzuzufügen, da dies bei einem blauen Mond nur einmal vorkommt.

Ryan Shillington
quelle
Ich fand dies die nützlichste aller bisherigen Antworten. Die letzte Zeile verhindert jedoch die Kompilierung in TypeScript. Ich denke nicht, dass Sie etwas von dieser Funktion zurückgeben sollten.
Freddie
0

Hier ist ein kleines Plugin dafür:

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

Das Zeitlimit für die automatische Inkrementierung wäre eine gute Ergänzung.

Um es global zu verwenden, erstellen Sie einfach Ihre eigene Funktion mit der Signatur $ .ajax, verwenden Sie dort die Wiederholungs-API und ersetzen Sie alle Ihre $ .ajax-Aufrufe durch Ihre neue Funktion.

Sie könnten auch $ .ajax direkt ersetzen, aber Sie können dann keine xhr-Aufrufe tätigen, ohne es erneut zu versuchen.

Oleg Isonen
quelle
0

Hier ist die Methode, die für mich beim asynchronen Laden von Bibliotheken funktioniert hat:

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

Rufen .load_scriptSie dann einfach von Ihrer App aus an und verschachteln Sie Ihren Erfolgsrückruf:

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

Die Antwort von DemoUsers funktioniert nicht mit Zepto, da dies in der Fehlerfunktion auf Window zeigt. (Und diese Art der Verwendung von 'this' ist nicht sicher genug, da Sie nicht wissen, wie sie Ajax implementieren oder nicht müssen.)

Für Zepto könnten Sie es vielleicht unten versuchen, bis jetzt funktioniert es gut für mich:

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

Verwenden Sie den Konstruktor, um sicherzustellen, dass die Anforderung erneut eingegeben wird!

Xhua
quelle
0

Dein Code ist fast voll :)

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
quelle