Как лучше всего повторить запрос AJAX в случае сбоя с помощью jQuery?

108

Псевдокод:

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

Еще лучше было бы какое-то экспоненциальное отставание

Том Леман
источник
1
Я не уверен, что это лучший способ вообще, поэтому просто комментарий, но если вы вызываете свой ajax из функции, вы можете указать ему параметр tries, а в случае неудачи вы вызовете свою функцию с помощью tries+1. Остановить выполнение на tries==3любом другом номере.
Nanne
Хорошее решение здесь: stackoverflow.com/questions/6298612/…
Джереми А. Уэст,

Ответы:

240

Что-то вроде этого:


$.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
Я взял решение @ Sudhir и создал плагин $ .retryAjax на github здесь: github.com/mberkom/jQuery.retryAjax
Michael Berkompas
2
У меня это не работает. this.tryCount в условном выражении всегда равно 1.
user304602 03 нояб.13,
2
@MichaelBerkompas - ваш плагин все еще работает? Не получал коммитов уже 2 года.
Хендрик
2
будет ли это работать, если другой обработчик обратного вызова, например .success, прикреплен к вызову функции, которая возвращает этот запрос ajax?
ProblemsOfSumit
17
Пара tryCountи retryLimitизлишне. Подумайте об использовании только 1 переменной:this.retryLimit--; if (this.retryLimit) { ... $.ajax(this) ... }
vladkras
15

Один из подходов - использовать функцию-оболочку:

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

Другой подход - использовать retriesсвойство на$.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);
}

Другой способ ( GIST ) - заменить оригинал $.ajax(лучше 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') 
});

Точку рассмотреть делает уверен$.ajax метод не был уже обернут ранее, для того , чтобы избежать такой же код работает в два раза.


Вы можете скопировать и вставить эти фрагменты (как есть) в консоль, чтобы протестировать их.

vsync
источник
Спасибо за сценарий. Это работает с $ .ajaxSetup?
Sevban Öztürk
@ SevbanÖztürk - что ты имеешь в виду? просто попробуйте :)
vsync 09
Спасибо, что научили меня структурировать оболочку! Это превосходит старый дизайн рекурсивных функций, который я использовал.
decoder7283 01
7

У меня был большой успех с этим кодом ниже (пример: 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
Единственное изменение, которое я бы предложил, - это заменить 'func ("' + param" '")' на function () {func (param)}. Таким образом, вы можете напрямую передавать параметр, не преобразовывая его в строку и обратно. , который может очень легко потерпеть неудачу!
fabspro
@fabspro Готово. Спасибо!
Набиль Кадими
7
Разве это не бесконечный цикл? Учитывая, что у вопроса есть retryLimit и он, очевидно, хочет обслужить сервер, который никогда не вернется ... Я думаю, что это действительно должно быть там
PandaWood
3
jQuery.ajaxSetup () Описание: установка значений по умолчанию для будущих запросов Ajax. Его использование не рекомендуется. api.jquery.com/jQuery.ajaxSetup
blub
2

Ни один из этих ответов не сработает, если кто-то позвонит .done()после своего вызова ajax, потому что у вас не будет метода успеха для присоединения к будущему обратному вызову. Итак, если кто-то сделает это:

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

Тогда mySuccessFuncне будет вызова при повторной попытке. Вот мое решение, которое в значительной степени заимствовано из ответа @cjpak здесь . В моем случае я хочу повторить попытку, когда AWS API Gateway отвечает с ошибкой 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);
});

Этот фрагмент отступит и попытается повторить попытку через 2 секунды, затем 5 секунд, затем 10 секунд, которые вы можете отредактировать, изменив константу RETRY_WAIT.

Служба поддержки AWS предложила добавить повторную попытку, так как это происходит у нас только один раз в синюю луну.

Райан Шиллингтон
источник
Я обнаружил, что это самый полезный из всех ответов на данный момент. Однако последняя строка предотвращает компиляцию в TypeScript. Я не думаю, что вам нужно что-то возвращать из этой функции.
Фредди
0

Вот небольшой плагин для этого:

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

Автоматическое увеличение тайм-аута было бы хорошим дополнением к этому.

Чтобы использовать его глобально, просто создайте свою собственную функцию с подписью $ .ajax, используйте там retry api и замените все ваши вызовы $ .ajax вашей новой функцией.

Также вы можете напрямую заменить $ .ajax, но тогда вы не сможете выполнять вызовы xhr без повторной попытки.

Олег Исонен
источник
0

Вот метод, который у меня сработал для асинхронной загрузки библиотек:

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

Затем просто позвоните .load_scriptиз своего приложения и вложите обратный вызов успеха:

$.loadScript('maps', '//maps.google.com/maps/api/js?v=3.23&libraries=geometry&libraries=places&language=&hl=&region=', function(){
    initialize_map();
    loadListeners();
});
Абрам
источник
0

Ответ DemoUsers не работает с Zepto, так как это в функции ошибки указывает на Window. (И такой способ использования this недостаточно безопасен, так как вы не знаете, как они реализуют ajax, или нет необходимости.)

Что касается Zepto, возможно, вы могли бы попробовать следующее, до сих пор у меня это хорошо работает:

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

Используйте конструктор, чтобы убедиться, что запрос повторяется!

Xhua
источник
0

Ваш код почти заполнен :)

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);
    }
});
Андрей
источник