Reputation:
I'd like to know if it's possible to handle the $promise
returned by ngResource on multiple levels so that the code is DRY
here is a simple example
aService = function(aResource) {
var error, success;
success = function(response) {
console.log('Service Success');
};
error = function(response) {
console.log('Service Error');
};
this.servicePostReq = function() {
return aResource.save().$promise.then(success, error);
};
return this;
angular.module('app.service').factory('aService', ['aResource', aService]);
this works fine so far... it Service Success
when response is OK and it Service Error
when response is not OK
but when I add a controller that use this aService
like following
aController = function(aService) {
var error, success;
success = function(response) {
console.log('Controller Success');
};
error = function(response) {
console.log('Controller Error');
};
this.controllerPostReq = function() {
aService.servicePostReq().then(success, error);
};
return this;
};
angular.module('app.controller').controller('aController', ['aService', aController]);
the controller always success...
so if the request return success the output is
Service Success
Controller Success
and if the request fails the output is
Service Error
Controller Success
how do I chain the promise so that I don't have to add the code handled in the service for every controller that use the service ?
Upvotes: 3
Views: 2424
Reputation: 1344
The problem is your service. Change this:
this.servicePostReq = function() {
return aResource.save().$promise.then(success, error);
};
To this:
this.servicePostReq = function() {
return aResource.save().$promise.then(success);
};
Explanation:
Since your service returns aResource.save().$promise.then(success, error)
, it's returning a new promise with an error handler included. Later, in your controller, you add onto the chain like this.
aService.servicePostReq().then(success, error);
The complete promise chain at this point looks if you expand it out:
return aResource.save().$promise
.then(successFnFromService, errorFnFromService)
.then(successFnFromController, errorFnFromController);
Since you catch the error from aResource.save()
with errorFnFromService
, the promise chain is basically "clean" at this point and it will just continue with the next then
.
By removing the first error handler, you allow the error to be caught later on.
A better way (in general) to handle errors in promise chains would be to use a single .catch() at the end of the chain.
Consider this bad code (try running on your browser console):
new Promise(
function(resolve, reject){
reject('first');
}).then(
function(result) {
console.log('1st success!', result);
return result;
},
function(err) {
console.log('1st error!', err);
return err;
}
).then(
function(result){
console.log('2nd success!', result);
},
function(err){
console.log("2nd error!", err);
}
);
Output:
1st error! first
2nd success! first
Better way:
new Promise(
function(resolve, reject){
reject('first');
}).then(function(result) {
console.log('1st success!', result);
return result;
}).then(function(result){
console.log('2nd success!', result);
// catch error once at the end
}).catch(function(err){
console.log("error!", err);
});
Output:
error! first
Try both of those in browser console, and change reject
to resolve
to see how it affects the output.
Upvotes: 4
Reputation: 6940
To properly chain promises, both success and error handlers should return some value. The return values are automatically wrapped in a new promise for you. This means that in the case of errors, you must return a rejected promise using $q.reject
.
So your service should look like this:
aService = function($q, aResource) {
var error, success;
success = function(response) {
// important! return a value, handlers down the chain will
// execute their success handlers and receive this value
return 'Service Success';
};
error = function(response) {
// important! return a REJECTION, handlers down the chain will
// execute their error handlers and receive this value
return $q.reject('Service Error');
};
this.servicePostReq = function() {
return aResource.save().$promise.then(success, error);
};
return this;
angular.module('app.service').factory('aService', ['$q', 'aResource', aService]);
Upvotes: 1
Reputation: 8055
add a dependency on the $q
and use $q.reject
to control the execution...
in your example you need a $q.reject
in the aService.error
method
as mentioned here in the $q docs
reject(reason);
Creates a promise that is resolved as rejected with the specified reason. This api should be used to forward rejection in a chain of promises. If you are dealing with the last promise in a promise chain, you don't need to worry about it.
When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of reject as the throw keyword in JavaScript. This also means that if you "catch" an error via a promise error callback and you want to forward the error to the promise derived from the current promise, you have to "rethrow" the error by returning a rejection constructed via reject.
Upvotes: 1