web-dev-qa-db-fra.com

Quelle est la meilleure façon de réessayer un AJAX demande en cas d'échec avec jQuery?

Pseudo code:

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

Encore mieux serait une sorte de recul exponentiel

88
Tom Lehman

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
        }
    }
});
202
Sudhir Bastakoti

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 propriété retries 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 ) - de remplacer le $.ajax original (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 faire que la méthode $.ajax n’ait pas déjà été encapsulée, afin d’éviter que le même code ne soit exécuté deux fois.


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

7
vsync

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 );
        });
}
4
Nabil Kadimi

La réponse de DemoUsers ne fonctionne pas avec Zepto, car celle-ci dans la fonction d'erreur pointe vers Window. (Et cette façon d'utiliser 'this' n'est pas assez sûre car vous ne savez pas comment ils implémentent ajax ou n'en ont pas besoin.)

Pour Zepto, vous pourriez peut-être essayer ci-dessous, jusqu'à maintenant, 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!

0
Xhua

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 de manière globale, créez simplement votre propre fonction avec la signature $ .ajax, utilisez-y, essayez de nouveau, et remplacez tous vos appels $ .ajax par votre nouvelle fonction.

Vous pouvez aussi remplacer directement $ .ajax, mais vous ne pourrez pas passer d’appels Xhr sans réessayer.

0
Oleg Isonen

Voici la méthode qui a fonctionné pour moi pour le chargement asynchrone de 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_script depuis 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();
});
0
Abram

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 à rattacher au prochain appel. Donc si quelqu'un fait ça:

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

Alors mySuccessFunc ne sera pas appelé lors de la nouvelle tentative. Voici ma solution, qui est fortement 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 va revenir en arrière et réessayer après 2 secondes, puis 5 secondes, puis 10 secondes, que vous pouvez éditer en modifiant la constante RETRY_WAIT.

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

0
Ryan Shillington