Ускоренное обслуживание службы 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
// ???
});
}
}]);
Мои вопросы:
- Как запросить услугу снова, когда я получаю сообщение об ошибке, не возвращая обещание.
- Было бы лучше сделать это в моем сервисе?
Спасибо!
Ответы
Ответ 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;
});
}
}]);