Quelle est la meilleure façon de réessayer une requête AJAX en cas d'échec à l'aide de jQuery?

107

Pseudo code:

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

Encore mieux serait une sorte de recul exponentiel

Tom Lehman
la source
1
Je ne suis pas sûr que ce soit la meilleure façon du tout, donc juste un commentaire, mais si vous appelez votre ajax à partir d'une fonction, vous pouvez lui donner un paramètre tries, et en cas d'échec, vous appelez votre fonction avec tries+1. Arrêtez l'exécution sur tries==3ou tout autre numéro.
Nanne
Bonne solution ici: stackoverflow.com/questions/6298612/…
Jeremy A. West

Réponses:

238

Quelque chose comme ça:


$.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
la source
12
J'ai pris la solution de @ Sudhir et créé un plugin $ .retryAjax sur github ici: github.com/mberkom/jQuery.retryAjax
Michael Berkompas
2
Cela ne fonctionne pas pour moi. this.tryCount dans le conditionnel est toujours 1.
user304602
2
@MichaelBerkompas - votre plugin fonctionne-t-il toujours? Il n'a pas reçu d'engagement depuis 2 ans.
Hendrik
2
cela fonctionnera-t-il si un autre gestionnaire de rappel comme .successest attaché à l'appel de la fonction qui renvoie cette requête ajax?
ProblemsOfSumit
17
Paire de tryCountet retryLimitest excessif. Envisagez d'utiliser seulement 1 variable:this.retryLimit--; if (this.retryLimit) { ... $.ajax(this) ... }
vladkras
15

Une approche consiste à utiliser une fonction 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);

Une autre approche consisterait à utiliser une retriespropriété sur le$.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);
}

Une autre façon ( GIST ) - remplacer l'original $.ajax(mieux pour 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 point à considérer est de s'assurer que la $.ajaxméthode n'a pas déjà été encapsulée précédemment, afin d'éviter que le même code ne s'exécute deux fois.


Vous pouvez copier-coller ces extraits (tels quels) dans la console pour les tester

vsync
la source
Merci pour le script. Fonctionne-t-il avec $ .ajaxSetup?
Sevban Öztürk
@ SevbanÖztürk - que voulez-vous dire? essayez simplement :)
vsync
Merci de m'avoir appris à structurer un wrapper! Cela bat l'ancienne conception de fonction récursive que j'avais l'habitude d'implémenter.
decoder7283
7

J'ai eu beaucoup de succès avec ce code ci-dessous (exemple: 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
la source
4
Le seul changement que je suggérerais est de remplacer 'func ("' + param" '")' par function () {func (param)}. De cette façon, vous pouvez directement transmettre le paramètre sans le convertir en chaîne et inversement , qui peut échouer très facilement!
fabspro
@fabspro Terminé. Merci!
Nabil Kadimi
7
N'est-ce pas une boucle sans fin? Étant donné que la question a un retryLimit et souhaite évidemment que le serveur ne revienne jamais ... Je pense que cela doit vraiment être là
PandaWood
3
jQuery.ajaxSetup () Description: Définit les valeurs par défaut pour les futures requêtes Ajax. Son utilisation n'est pas recommandée. api.jquery.com/jQuery.ajaxSetup
blub
2

Aucune de ces réponses ne fonctionne si quelqu'un appelle .done()après son appel ajax car vous n'aurez pas la méthode de réussite pour vous attacher au futur rappel. Donc, si quelqu'un fait ça:

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

Ensuite mySuccessFunc, vous ne serez pas appelé lors de la nouvelle tentative. Voici ma solution, qui est largement empruntée à la réponse de @ cjpak ici . Dans mon cas, je souhaite réessayer lorsque la passerelle API d'AWS répond avec une erreur 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);
});

Cet extrait de code s'arrêtera et réessayera après 2 secondes, puis 5 secondes, puis 10 secondes, que vous pouvez modifier en modifiant la constante RETRY_WAIT.

Le support AWS nous a suggéré d'ajouter une nouvelle tentative, car cela ne se produit qu'une seule fois dans une lune bleue.

Ryan Shillington
la source
J'ai trouvé que c'était la plus utile de toutes les réponses à ce jour. Cependant, la dernière ligne empêche la compilation dans TypeScript. Je ne pense pas que vous devriez renvoyer quoi que ce soit de cette fonction.
Freddie
0

Voici un petit plugin pour cela:

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

Le délai d'incrémentation automatique serait un bon ajout à cela.

Pour l'utiliser globalement, créez simplement votre propre fonction avec la signature $ .ajax, utilisez-la pour réessayer api et remplacez tous vos appels $ .ajax par votre nouvelle fonction.

Vous pouvez également remplacer directement $ .ajax, mais vous ne pourrez pas faire d'appels xhr sans réessayer.

Oleg Isonen
la source
0

Voici la méthode qui a fonctionné pour moi pour le chargement asynchrone des bibliothèques:

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

Ensuite, appelez simplement .load_scriptdepuis votre application et imbriquez votre rappel de réussite:

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

La réponse de DemoUsers ne fonctionne pas avec Zepto, car cela dans la fonction d'erreur pointe vers Window. (Et cette façon d'utiliser 'this' n'est pas suffisamment sécurisée car vous ne savez pas comment ils implémentent ajax ou pas besoin de le faire.)

Pour Zepto, vous pourriez peut-être essayer ci-dessous, jusqu'à présent, cela fonctionne bien pour moi:

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

Utilisez le constructeur pour vous assurer que la requête est réentrante!

Xhua
la source
0

Votre code est presque plein :)

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
la source