Qual è il modo migliore per ritentare una richiesta AJAX in caso di errore utilizzando jQuery?


107

Pseudo codice:

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

Ancora meglio sarebbe una sorta di arretramento esponenziale


1
Non sono sicuro che questo sia il modo migliore, quindi solo un commento, ma se chiami il tuo ajax da una funzione, puoi dargli un parametro triese, in caso negativo, chiami la tua funzione con tries+1. Interrompi l'esecuzione su tries==3o qualsiasi altro numero.
Nanne


Risposte:


238

Qualcosa come questo:


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

12
Ho preso la soluzione di @ Sudhir e ho creato un plugin $ .retryAjax su github qui: github.com/mberkom/jQuery.retryAjax
Michael Berkompas

2
Questo non funziona per me. this.tryCount nel condizionale è sempre 1.
user304602

2
@MichaelBerkompas - il tuo plugin funziona ancora? Non ha ricevuto impegni da 2 anni.
Hendrik

2
funzionerà se un altro gestore di callback come .successè collegato alla chiamata della funzione che restituisce questa richiesta ajax?
ProblemsOfSumit

17
Coppia di tryCounted retryLimitè eccessivo. Considera l'utilizzo di una sola variabile:this.retryLimit--; if (this.retryLimit) { ... $.ajax(this) ... }
vladkras

15

Un approccio consiste nell'usare una funzione 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);

Un altro approccio potrebbe essere quello di utilizzare una retriesproprietà in$.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);
}

Un altro modo ( GIST ): sostituisci l'originale $.ajax(meglio per 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 punto da considerare è assicurarsi che il $.ajaxmetodo non fosse già avvolto in precedenza, al fine di evitare che lo stesso codice venga eseguito due volte.


Puoi copiare e incollare questi frammenti (così come sono) nella console per testarli


Grazie per la sceneggiatura. Funziona con $ .ajaxSetup?
Sevban Öztürk

@ SevbanÖztürk - cosa intendi? provaci :)
vsync

Grazie per avermi insegnato a strutturare un wrapper! Questo batte il vecchio design della funzione ricorsiva che ho usato per implementare.
decoder 7283

7

Ho avuto molto successo con questo codice qui sotto (esempio: 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
L'unica modifica che suggerirei è di sostituire 'func ("' + param" '")' con function () {func (param)}. In questo modo, puoi passare direttamente il parametro senza convertirlo in una stringa e viceversa , che può fallire molto facilmente!
fabspro

@fabspro Fatto. Grazie!
Nabil Kadimi

7
Non è un ciclo infinito? Data la domanda ha un retryLimit e ovviamente vuole soddisfare il server che non torna mai più ... Penso che questo debba davvero essere lì
PandaWood

3
jQuery.ajaxSetup () Descrizione: imposta i valori predefiniti per future richieste Ajax. Il suo utilizzo è sconsigliato. api.jquery.com/jQuery.ajaxSetup
blub

2

Nessuna di queste risposte funziona se qualcuno chiama .done()dopo la chiamata ajax perché non avrai il metodo di successo da allegare alla futura richiamata. Quindi, se qualcuno fa questo:

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

Quindi mySuccessFuncnon verrai chiamato per il nuovo tentativo. Ecco la mia soluzione, che è ampiamente presa in prestito dalla risposta di @ cjpak qui . Nel mio caso, voglio riprovare quando API Gateway di AWS risponde con l'errore 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);
});

Questo frammento tornerà indietro e riproverà dopo 2 secondi, quindi 5 secondi, quindi 10 secondi, che puoi modificare modificando la costante RETRY_WAIT.

Il supporto AWS ha suggerito di aggiungere un nuovo tentativo, poiché per noi avviene solo una volta in una luna blu.


Ho trovato che questa sia la più utile di tutte le risposte finora. Tuttavia, l'ultima riga impedisce la compilazione in TypeScript. Non penso che dovresti restituire nulla da questa funzione.
Freddie

0

Ecco un piccolo plugin per questo:

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

Il timeout di incremento automatico sarebbe una buona aggiunta.

Per usarlo a livello globale, crea la tua funzione con la firma $ .ajax, usa l'API retry e sostituisci tutte le chiamate $ .ajax con la tua nuova funzione.

Inoltre potresti sostituire direttamente $ .ajax, ma non sarai in grado di effettuare chiamate xhr senza riprovare.


0

Ecco il metodo che ha funzionato per me per il caricamento asincrono delle librerie:

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

Quindi chiama .load_scriptdalla tua app e annida la tua richiamata di successo:

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

0

La risposta di DemoUsers non funziona con Zepto, poiché questo nella funzione di errore punta a Window. (E quel modo di usare 'questo' non è abbastanza sicuro perché non sai come implementano ajax o non è necessario.)

Per Zepto, forse potresti provare di seguito, fino ad ora funziona bene per me:

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

Usa il costruttore per assicurarti che la richiesta sia rientrante!


0

Il tuo codice è quasi pieno :)

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);
    }
});
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.