问题
I'm new to angular's $q and I'm trying to build a service that makes an API call and returns it back to the controller.
Problem: No matter how I seem to format it, the service returns
right before it gets to $http.get(...)
Service:
// methods: query new, get existing
makeRequest: function(url, following) {
// create promise
var deferred = $q.defer();
$http.get(url, {
params: {
"following": JSON.stringify(following)
}
})
.then(function(res) {
console.log(res);
deferred.resolve(res.data);
});
return deferred.promise;
},
getFeed: function(user) {
console.log('>> userService[getUser]: retrieving user...');
if (!this.activities) {
// Request has not been made, setting user profile.
console.log('>> userService[getUser]: No user stored, making request...');
var following = this.compileArray(user);
console.log(following);
this.activities = this.makeRequest('api/network/activities', following);
};
// Return the myObject stored on the service
return this.activities;
}
Controller
$scope.recentActivity = activityService.getFeed(profile);
// also tried
activityService.getFeed(profile).then(function (res) {
$scope.recentActivity = res;
console.log(res);
});
EDIT : 9:40am 05/06/2015
If possible, I'd like to retrieve the activity list in the controller from the service in the same way I would if it were new (using .then
). Is that possible/ bad practice?
getFeed: function(user) {
if (!this.activities) {
...
} else {
feedPromise = $q(function(resolve){ resolve(this.activities) });
console.log(feedPromise);
// returns: {$$state: Object, then: function, catch: function, finally: function}
feedPromise.then(function(res) {
console.log(res);
// returns: undefined
});
console.log(that.activities);
// Works, returns list of activities.
}
回答1:
There is no need to use a $q.defer
unless you are converting a non-promise based API with callbacks into a promise-based API (and even then, it is recommended to use $q(function(resolve, reject){...})
).
$http
already returns a promise - just return that (or a chained .then
promise);
var httpResponsePromise = $http.get(url); // returns a promise
var actualDataPromise = httpResponsePromise.then(function(resp){ return resp.data; });
return actualDataPromise;
or shorter (and typical):
return $http.get(url).then(function(response){
return response.data;
});
Second, a promise-returning API returns the promise - not the result - right away, synchronously. You need a .then
to get the result.
Lastly, once an API is async, it should always be async - don't convert it to a sync or a sometimes-sync API. So, everywhere, all the way to the end recipient of the data, you need to use a .then
handler.
So, your service API can be made quite simple:
makeRequest: function(url, following){
return $http.get(url, {params: { "following": following }})
.then(function(response){
return response.data;
});
},
getFeed: function(user) {
var that = this;
var feedPromise;
if (!that.activities) {
var following = this.compileArray(user);
feedPromise = this.makeRequest('api/network/activities', following)
.then(function(activities){
that.activities = activities;
return activities;
});
} else {
feedPromise = $q(function(resolve){ resolve(that.activities); });
// or you could have cached the old feedPromise and returned that
}
return feedPromise;
}
The usage in the controller is just like with any other promise-based API:
activityService.getFeed(profile)
.then(function(activities) {
$scope.recentActivity = activities;
});
回答2:
After learning about the deferred anti-pattern as pointed out in the comments by @New Dev, I have edited this answer. Please see @New Dev's answer for a detailed explanation. Thanks @New Dev.
Service
makeRequest: function(url, following) {
return $http.get(url, {
params: {
"following": JSON.stringify(following)
}
}).then(function(res) {
return res.data;
});
},
getFeed: function(user) {
var that = this;
if (!that.activities) {
var following = that.compileArray(user);
return that.makeRequest('api/network/activities', following)
.then(function(activities){
that.activities = activities;
return that.activities;
});
} else {
return $q(function(resolve) {
resolve(that.activities);
});
}
}
Controller
activityService
.getFeed(profile)
.then(function (activities) {
$scope.recentActivity = activities;
});
回答3:
i think you need to use .success instead of .then if you want to have the response object.
$http.get(url, {
params: {
"following": JSON.stringify(following)
}
})
.success(function(res) {
console.log(res);
deferred.resolve(res.data);
});
来源:https://stackoverflow.com/questions/30043495/q-defer-not-working-with-angular-service