I'm using $http
in AngularJs, and I'm not sure on how to use the returned promise and to handle errors.
I have this code:
$http
.get(url)
.success(function(data) {
// Handle data
})
.error(function(data, status) {
// Handle HTTP error
})
.finally(function() {
// Execute logic independent of success/error
})
.catch(function(error) {
// Catch and handle exceptions from success/error/finally functions
});
Is this a good way to do it, or is there an easier way?
Promises are an abstraction over statements that allow us to express ourselves synchronously with asynchronous code. They represent a execution of a one time task.
They also provide exception handling, just like normal code, you can return from a promise or you can throw.
What you'd want in synchronous code is:
try{
try{
var res = $http.getSync("url");
res = someProcessingOf(res);
} catch (e) {
console.log("Got an error!",e);
throw e; // rethrow to not marked as handled
}
// do more stuff with res
} catch (e){
// handle errors in processing or in error.
}
The promisified version is very similar:
$http.get("url").
then(someProcessingOf).
catch(function(e){
console.log("got an error in initial processing",e);
throw e; // rethrow to not marked as handled,
// in $q it's better to `return $q.reject(e)` here
}).then(function(res){
// do more stuff
}).catch(function(e){
// handle errors in processing or in error.
});
Forget about using success
and error
method.
Both methods have been deprecated in angular 1.4. Basically, the reason behind the deprecation is that they are not chainable-friendly, so to speak.
With the following example, I'll try to demonstrate what I mean about success
and error
being not chainable-friendly. Suppose we call an API that returns a user object with an address:
User object:
{name: 'Igor', address: 'San Francisco'}
Call to the API:
$http.get('/user')
.success(function (user) {
return user.address; <---
}) | // you might expect that 'obj' is equal to the
.then(function (obj) { ------ // address of the user, but it is NOT
console.log(obj); // -> {name: 'Igor', address: 'San Francisco'}
});
};
What happened?
Because success
and error
return the original promise, i.e. the one returned by $http.get
, the object passed to the callback of the then
is the whole user object, that is to say the same input to the preceding success
callback.
If we had chained two then
, this would have been less confusing:
$http.get('/user')
.then(function (user) {
return user.address;
})
.then(function (obj) {
console.log(obj); // -> 'San Francisco'
});
};
I think the previous answers are correct, but here is another example (just a f.y.i, success() and error() are deprecated according to AngularJS Main page:
$http
.get('http://someendpoint/maybe/returns/JSON')
.then(function(response) {
return response.data;
}).catch(function(e) {
console.log('Error: ', e);
throw e;
}).finally(function() {
console.log('This finally block');
});
What type of granularity are you looking for? You can typically get by with:
$http.get(url).then(
//success function
function(results) {
//do something w/results.data
},
//error function
function(err) {
//handle error
}
);
I've found that "finally" and "catch" are better off when chaining multiple promises.
In Angular $http case, the success() and error() function will have response object been unwrapped, so the callback signature would be like $http(...).success(function(data, status, headers, config))
for then(), you probably will deal with the raw response object. such as posted in AngularJS $http API document
$http({
url: $scope.url,
method: $scope.method,
cache: $templateCache
})
.success(function(data, status) {
$scope.status = status;
$scope.data = data;
})
.error(function(data, status) {
$scope.data = data || 'Request failed';
$scope.status = status;
});
The last .catch(...) will not need unless there is new error throw out in previous promise chain.
I do it like Bradley Braithwaite suggests in his blog:
app
.factory('searchService', ['$q', '$http', function($q, $http) {
var service = {};
service.search = function search(query) {
// We make use of Angular's $q library to create the deferred instance
var deferred = $q.defer();
$http
.get('http://localhost/v1?=q' + query)
.success(function(data) {
// The promise is resolved once the HTTP call is successful.
deferred.resolve(data);
})
.error(function(reason) {
// The promise is rejected if there is an error with the HTTP call.
deferred.reject(reason);
});
// The promise is returned to the caller
return deferred.promise;
};
return service;
}])
.controller('SearchController', ['$scope', 'searchService', function($scope, searchService) {
// The search service returns a promise API
searchService
.search($scope.query)
.then(function(data) {
// This is set when the promise is resolved.
$scope.results = data;
})
.catch(function(reason) {
// This is set in the event of an error.
$scope.error = 'There has been an error: ' + reason;
});
}])
Key Points:
The resolve function links to the .then function in our controller i.e. all is well, so we can keep our promise and resolve it.
The reject function links to the .catch function in our controller i.e. something went wrong, so we can’t keep our promise and need to reject it.
It is quite stable and safe and if you have other conditions to reject the promise you can always filter your data in the success function and call deferred.reject(anotherReason)
with the reason of the rejection.
As Ryan Vice suggested in the comments, this may not be seen as useful unless you fiddle a bit with the response, so to speak.
Because success
and error
are deprecated since 1.4 maybe it is better to use the regular promise methods then
and catch
and transform the response within those methods and return the promise of that transformed response.
I am showing the same example with both approaches and a third in-between approach:
success
and error
approach (success
and error
return a promise of an HTTP response, so we need the help of $q
to return a promise of data):
function search(query) {
// We make use of Angular's $q library to create the deferred instance
var deferred = $q.defer();
$http.get('http://localhost/v1?=q' + query)
.success(function(data,status) {
// The promise is resolved once the HTTP call is successful.
deferred.resolve(data);
})
.error(function(reason,status) {
// The promise is rejected if there is an error with the HTTP call.
if(reason.error){
deferred.reject({text:reason.error, status:status});
}else{
//if we don't get any answers the proxy/api will probably be down
deferred.reject({text:'whatever', status:500});
}
});
// The promise is returned to the caller
return deferred.promise;
};
then
and catch
approach (this is a bit more difficult to test, because of the throw):
function search(query) {
var promise=$http.get('http://localhost/v1?=q' + query)
.then(function (response) {
// The promise is resolved once the HTTP call is successful.
return response.data;
},function(reason) {
// The promise is rejected if there is an error with the HTTP call.
if(reason.statusText){
throw reason;
}else{
//if we don't get any answers the proxy/api will probably be down
throw {statusText:'Call error', status:500};
}
});
return promise;
}
There is a halfway solution though (this way you can avoid the throw
and anyway you'll probably need to use $q
to mock the promise behavior in your tests):
function search(query) {
// We make use of Angular's $q library to create the deferred instance
var deferred = $q.defer();
$http.get('http://localhost/v1?=q' + query)
.then(function (response) {
// The promise is resolved once the HTTP call is successful.
deferred.resolve(response.data);
},function(reason) {
// The promise is rejected if there is an error with the HTTP call.
if(reason.statusText){
deferred.reject(reason);
}else{
//if we don't get any answers the proxy/api will probably be down
deferred.reject({statusText:'Call error', status:500});
}
});
// The promise is returned to the caller
return deferred.promise;
}
Any kind of comments or corrections are welcome.
来源:https://stackoverflow.com/questions/23559341/using-success-error-finally-catch-with-promises-in-angularjs