拒绝承诺时AngularJS服务重试
Posted
技术标签:
【中文标题】拒绝承诺时AngularJS服务重试【英文标题】:AngularJS service retry when promise is rejected 【发布时间】:2013-11-04 22:17:06 【问题描述】:我正在从控制器内部的异步服务中获取数据,如下所示:
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
// ???
);
]);
我的问题:
-
当我从服务中得到错误而不返回承诺时如何再次查询服务。
在我的服务中这样做会更好吗?
谢谢!
【问题讨论】:
是的,在服务中重新启动,这样在控制器中您就可以简单地获得已解析的数据。 定义你的函数并命名它。在拒绝调用它。就这么简单! 试图在控制器中返回 $scope.getData(query) 但不再发送承诺 【参考方案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;
);
]);
【讨论】:
可能想要一个计数器,如果服务器宕机了,这只会一遍又一遍地循环。 或者它应该是一个退避,每次失败后都有 x2 延迟? 有没有办法把它变成一个通用的 http 包装类型场景。对每个 http 调用自动进行这种检查? @AugieGardner 也许通过在 Angular 中使用应用程序级别的$http
请求拦截器。
关于实现指数退避(每次失败后的 2 倍延迟)的一些注意事项:developers.google.com/analytics/devguides/reporting/core/v3/…【参考方案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 中
【讨论】:
在 .error 中,最好添加: else results.reject(path + ' failed after ' + MAX_REQUESTS + ' retries.'); 或许可以通过results.reject(...)
正确处理错误
所以我试了一下,效果很好(3个错误后拒绝)【参考方案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】:我最终做了很多,所以我写了一个库来帮助解决这个问题:)
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;
);
]);
【讨论】:
【参考方案5】:关注本文Promises in AngularJS, Explained as a Cartoon
只有在响应属于 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(, );
);
【讨论】:
5XX 错误的好选择。当 error.status 等于 -1(超时,无连接,...)时,也值得重试。以上是关于拒绝承诺时AngularJS服务重试的主要内容,如果未能解决你的问题,请参考以下文章
未处理的承诺拒绝:this._next 不是函数:@angular/fire/messaging 中的区域
如何模拟在 AngularJS Jasmine 单元测试中返回承诺的服务?