问题
As per my other recent questions, I'm trying to persist data to a server for an angular application that's targeted at mobile devices (unstable connections), so it should keep trying the request until success.
How can I do this with promises?
At the moment I've got:
Service:
this.addObject = function addObject(object) {
var deferred = $q.defer();
var httpConfig = {
method: 'POST',
url: 'http://api.foo.com/bar',
data: object
}
setTimeout(function() {
$http(httpConfig).
success(function(data, status) {
deferred.resolve('Woohoo!');
}).
error(function(data, status) {
deferred.reject('Couldnt reach server this time...');
});
}, 3000);
return deferred.promise;
}
Controller:
myService.addObject(myObject)
.then(function(message) {
console.log(message);
}, function(message) {
console.log(message);
});
I can't remove the reject callback, as the code won't seem to execute without it, but once the reject is called, it breaks the setTimeout loop. How can I force the promise to repeat until the success callback?
回答1:
This is the proper form of the answer at AngularJS service retry when promise is rejected
this.addObject = function addObject(object) {
var counter = 0;
var deferred = $q.defer();
var httpConfig = {
method: 'POST',
url: 'http://api.foo.com/bar',
data: object
}
var doRequest = function() {
counter++;
var self = this,args = arguments;
$http(httpConfig).
success(function(data, status) {
deferred.resolve('Woohoo!');
}).
error(function(data, status) {
//just fail already, it's not working
if(counter > 5) {
return deferred.reject('Couldnt reach server this time...');
}
//this will re-call doRequest();
args.callee.apply(self);
});
}
doRequest();
return deferred.promise;
}
回答2:
As you're doing with with $http
, http interceptors can do this. If you want a http request to infinitely loop until it returns a success:
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('RetryInterceptor');
}]);
app.factory('RetryInterceptor', function($timeout, $injector) {
return {
'responseError': function(rejection) {
// Manual inject to work around circular dependency issue
var $http = $injector.get('$http');
return $timeout(function() {
return $http(rejection.config);
},3000);
}
}
});
This works due to how the initial call to $http won't resolve until (in this case) all the responseError interceptors have been resolved.
You can see this in action at http://plnkr.co/edit/QAa9oIK4lTM6YwccEed3?p=preview (looking in the console log there is a failed request every 3 seconds.)
Note: there might need to be more logic to make sure it only retries on the right sort of error. i.e. it's not a real 404 where in fact the browser is to blame, for example.
回答3:
it should keep trying the request until success. How can I do this with promises?
By "recursively" calling the function again in the error handler so that you're resolving the promise with the result of the next try.
this.addObject = function addObject(object) {
var httpConfig = {
method: 'POST',
url: 'http://api.foo.com/bar',
data: object
}
return $http(httpConfig).then(function(data, status) {
return 'Woohoo!';
}, function(data, status) {
throw 'Couldnt reach server this time...';
});
}
this.addObjectForever = function addObject(object) {
var that = this;
return this.addObject(object).then(null, function(err) {
console.log("Failed this time");
return $timeout(3000).then(function() {
console.log("Trying again now");
return that.addObjectForever(object);
});
});
};
instance.addObjectForever(obj).done(function() {
console.log("It eventually worked");
});
回答4:
quite crowded here :) my solution:
angular.module('app', [])
.service('Service',function($q,$http) {
this.addObject = function(object) {
var httpConfig = {
method: 'POST',
url: 'http://api.foo.com/bar',
data: object
}
var deferred = $q.defer();
$http(httpConfig)
.success(function(data, status) {
deferred.resolve('Woohoo!');
})
.error(function(data, status) {
deferred.reject('Couldnt reach server this time...');
});
return deferred.promise;
};
})
.controller('MainCtrl',function($scope,$interval,Service){
/*Service.addObject({})
.then(function(message) {
console.log(message);
}, function(message) {
console.log(message);
});*/
$interval(function(){
Service.addObject().then(function(message) {
console.log(message);
}, function(message) {
console.log(message);
});
},1000);
})
回答5:
Uhm. Even though there are HTTP interceptors for this particular case, one of the advantages of working with promises instead of callback is that you can use higher order functions.
// both p and q are functions that create a promise
p = makeSomePromise(options)
q = repeat(10, p)
That is, for instance, a function that takes a promise making function and produces a new promise making function that retries the other promise repeatedly, until a max.
For example: (This is for nodeJS using kew
, but you should get the point)
var Q = require('kew');
function retryPromise(n, mkPromise) {
if(n > 0)
return mkPromise()
.fail(function(){
console.log('failed promise, retrying maximum ' + n + ' more times');
return retryPromise(n - 1, mkPromise);
});
return mkPromise();
}
(function(){
var mkOp = function(){
if(Math.random() > .1)
return Q
.fcall(function(){return 1;})
.then(function(){throw Error('aah');});
return Q.fcall(function(){return 'yay'});
};
retryPromise(10, mkOp)
.then(function(data){
console.log(data);
})
.fail(function(err){
console.log('failed', err);
});
}());
来源:https://stackoverflow.com/questions/21146856/using-the-q-implementation-in-angular-how-can-i-loop-a-promise-until-success