Подтвердить что ты не робот

Ускоренное обслуживание службы AngularJS, когда обещание отклонено

Я получаю данные от асинхронной службы внутри своего контроллера следующим образом:

myApp.controller('myController', ['$scope', 'AsyncService',
function($scope, AsyncService) {
    $scope.getData = function(query) {
        return AsyncService.query(query).then(function(response) {
            // Got success response, return promise
            return response;
        }, function(reason) {
            // Got error, query again in one second
            // ???
        });
    }
}]);

Мои вопросы:

  • Как запросить услугу снова, когда я получаю сообщение об ошибке, не возвращая обещание.
  • Было бы лучше сделать это в моем сервисе?

Спасибо!

4b9b3361

Ответ 1

Вы можете повторить запрос в самой службе, а не в контроллере.

Итак, AsyncService.query может выглядеть примерно так:

AsyncService.query = function() {
  var counter = 0
  var queryResults = $q.defer()

  function doQuery() {
    $http({method: 'GET', url: 'https://example.com'})
      .success(function(body) {
        queryResults.resolve(body)
      })
      .error(function() {
        if (counter < 3) {
          doQuery()
          counter++ 
        }
      })
  }

  return queryResults.promise
}

И вы можете избавиться от своей функции ошибки в контроллере:

myApp.controller('myController', ['$scope', 'AsyncService',
  function($scope, AsyncService) {
    $scope.getData = function(query) {
      return AsyncService.query(query).then(function(response) {
        // Got success response
        return response;
      });
    }
  }
]);

Ответ 2

Это действительно работает:

angular.module('retry_request', ['ng'])
  .factory('RetryRequest', ['$http', '$q', function($http, $q) {
    return function(path) {
      var MAX_REQUESTS = 3,
          counter = 1,
          results = $q.defer();

      var request = function() {
        $http({method: 'GET', url: path})
          .success(function(response) {
            results.resolve(response)
          })
          .error(function() {
            if (counter < MAX_REQUESTS) {
              request();
              counter++;
            } else {
              results.reject("Could not load after multiple tries");
            }
          });
      };

      request();

      return results.promise;
    }
  }]);

Тогда просто пример его использования:

RetryRequest('/api/token').then(function(token) {
  // ... do something
});

Вы должны потребовать его при объявлении своего модуля:

angular.module('App', ['retry_request']);

И у вас контроллер:

app.controller('Controller', function($scope, RetryRequest) {
  ...
});

Если кто-то хочет улучшить его с помощью какой-либо отсрочки или случайного времени, чтобы повторить запрос, это будет еще лучше. Я хочу, чтобы в один прекрасный день что-то вроде этого будет в Angular Core

Ответ 3

Я написал реализацию с экспоненциальным отсрочкой, которая не использует рекурсию (которая создавала бы вложенные фреймы стека, правильно?) Способ, которым он реализован, имеет стоимость использования нескольких таймеров и всегда создает все стоковые кадры для make_single_xhr_call ( даже после успеха, а не только после неудачи). Я не уверен, стоит ли это (особенно если средний случай - это успех), но это пища для размышлений.

Меня беспокоило состояние гонки между вызовами, но если javascript является однопоточным и не имеет контекстных переключателей (что позволило бы одному $http.success быть прерванным другим и разрешить ему выполнять дважды), тогда мы хорошо здесь, правильно?

Кроме того, я очень новичок в angularjs и современном javascript, поэтому соглашения могут быть немного грязными. Дайте мне знать, что вы думаете.

var app = angular.module("angular", []);

app.controller("Controller", ["$scope", "$http", "$timeout",
    function($scope, $http, $timeout) {

  /**
   * Tries to make XmlHttpRequest call a few times with exponential backoff.
   * 
   * The way this works is by setting a timeout for all the possible calls
   * to make_single_xhr_call instantly (because $http is asynchronous) and
   * make_single_xhr_call checks the global state ($scope.xhr_completed) to
   * make sure another request was not already successful.
   *
   * With sleeptime = 0, inc = 1000, the calls will be performed around:
   * t = 0
   * t = 1000 (+1 second)
   * t = 3000 (+2 seconds)
   * t = 7000 (+4 seconds)
   * t = 15000 (+8 seconds)
   */
  $scope.repeatedly_xhr_call_until_success = function() {
    var url = "/url/to/data";
    $scope.xhr_completed = false
    var sleeptime = 0;
    var inc = 1000;
    for (var i = 0, n = 5 ; i < n ; ++i) {
      $timeout(function() {$scope.make_single_xhr_call(url);}, sleeptime);
      sleeptime += inc;
      inc = (inc << 1); // multiply inc by 2
    }
  };

  /**
   * Try to make a single XmlHttpRequest and do something with the data.
   */
  $scope.make_single_xhr_call = function(url) {
    console.log("Making XHR Request to " + url);

    // avoid making the call if it has already been successful
    if ($scope.xhr_completed) return;
    $http.get(url)
      .success(function(data, status, headers) {
        // this would be later (after the server responded)-- maybe another
        // one of the calls has already completed.
        if ($scope.xhr_completed) return;
        $scope.xhr_completed = true;
        console.log("XHR was successful");
        // do something with XHR data
      })
      .error(function(data, status, headers) {
        console.log("XHR failed.");
      });
  };

}]);

Ответ 4

Следуя этой статье Promises в AngularJS, объясняется как мультфильм

вам нужно повторить попытку, только когда ответ попадает под категорию 5XX

Я написал службу под названием http, которую можно вызывать, передавая все http-конфиги как

 var params = {
  method: 'GET',
  url: URL,
  data: data
 }

затем вызовите метод службы следующим образом:

   <yourDefinedAngularFactory>.http(params, function(err, response) {});

http: function(config, callback) {
  function request() {
    var counter = 0;
    var queryResults = $q.defer();

    function doQuery(config) {
      $http(config).success(function(response) {
        queryResults.resolve(response);
      }).error(function(response) {
        if (response && response.status >= 500 && counter < 3) {
          counter++;
          console.log('retrying .....' + counter);
          setTimeout(function() {
            doQuery(config);
          }, 3000 * counter);
        } else {
          queryResults.reject(response);
        }
      });
    }
    doQuery(config);
    return queryResults.promise;
  }
  request(config).then(function(response) {
    if (response) {
      callback(response.errors, response.data);
    } else {
      callback({}, {});
    }
  }, function(response) {
    if (response) {
      callback(response.errors, response.data);
    } else {
      callback({}, {});
    }
  });
}

Ответ 5

В итоге я много сделал, поэтому я написал библиотеку, чтобы помочь решить эту проблему:)

https://www.npmjs.com/package/reattempt-promise-function

В этом примере вы можете сделать что-то вроде

myApp.controller('myController', ['$scope', 'AsyncService',
function($scope, AsyncService) {
    var dogsQuery = { family: canine };
    $scope.online = true;
    $scope.getDogs = function() {
        return reattempt(AsyncService.query(dogsQuery)).then(function(dogs) {
            $scope.online = true;
            $scope.dogs = dogs;
        }).catch(function() {
            $scope.online = false;
        });
    }
}]);