I have an $http request that is handled by a success and error function, and there is something I would like to do with the response in both cases:
$http(params)
.then(success, error);
var success = function(response) {
// do stuff
foo(response);
}
var error = function(response) {
// do other stuff
foo(response);
}
I'd prefer not to repeat code in both handlers, and I thought I might use finally to solve this problem but it seems the finally function doesn't receive any arguments.
Am I stuck calling foo(response) from both the success and the error function? Or is there a better way? (please say there is a better way...)
What you can do is convert the failure into success:
$http(params)
.then(success, error)
.then(foo);
var success = function(response) {
// do stuff
return response;
}
var error = function(response) {
// do other stuff
return response;
}
finally callback is called with no arguments. The manual reasonably explains this:
finally(callback, notifyCallback) – allows you to observe either the
fulfillment or rejection of a promise, but to do so without modifying
the final value.
This behaviour complies with other promise implementations (particularly Q, which was the inspiration for $q).
The pattern for result processing may be
$http(...)
.catch(function (err) {
// condition err response
return err;
})
.then(function (result) {
// ...
});
This isn't the same as finally, because this chain results in fulfilled promise (unless the rejection took place in then), while finally doesn't affect chain state (unless the rejection took place in finally).
Related
I thought that I had this all figured out on previous projects through the years.. Apparently not.
Goal : Take Service that calls other Services and if there is any type of error being returned ( not a status of 200 ) then I need the async thing to be waiting and not proceeding.
Seems to me like I don't ever see really that great of examples as it is all very simplistic.
I read various articles about what Angular (1) is doing under the hood , and i see that there are $q, .then, .success etc..
Seems that I am having issues with return and with other nested and bundled service calls being made without any checking of a problem.
Essentially this image shows what is coming back
data : null ( that is bad)
errorList Array1 0 "This order cannot be submitted... " ( bad too)
hasErrors : true ( bad as well)
So that data is important to me to capture and display to user and then NOT move onto more processing
This is my order of operations
this.submitEnrollment = function (enrollment) {
return getSubmit(requestData);
}
// Which CALLS below
var getSubmit = function (request) {
return SparkRequestService
.submitRequest(request)
.then(
function (resData) {
console.log("resData", resData);
enrollmentService.resetEnrollment();
return resData;
},
function (resData) {
console.log('error');
}
);
}
Then I'm certainly calling SparkRequestService.submitRequest(request)
but based on the image attached, I am getting the error in the resData
So, it seems that I need to interrogate the resData right? So then I really should NOT ALLOW this other service to be called enrollmentService.resetEnrollment();
How can i refactor to stop from that getting processed? if statement in the .then ?
To prevent a rejection handler from converting a rejected promise to a fulfilled promise it is important use a throw statement in the rejection handler:
var getSubmit = function (request) {
return SparkRequestService
.submitRequest(request)
.then(
function (resData) {
console.log("resData", resData);
enrollmentService.resetEnrollment();
return resData;
},
function (errorResponse) {
console.log('error');
//IMPORTANT
//throw to chain rejection
throw errorResponse;
}
);
}
When a function omits a return or throw statement, the function returns a value of undefined. This will convert a rejected promise to a fulfilled promise that resolves with a value of undefined.
Problem is that ... it is a business error wrapped up in a return object
To convert a fulfilled promise to a rejected promise, use a throw statement.
this.submitEnrollment = function (enrollment) {
var promise = getSubmit(requestData);
var newPromise = promise.then(function(response) {
if (response.data.hasErrors) {
console.log(response.data.errorList);
response.data.errorList.push("submitEnrollent: Rejected");
//THROW to create rejection
throw response;
} else {
//RETURN response to chain success
return response;
}
});
return newPromise;
}
When a promise is converted to a rejection, all subsequent success handlers in the chain will be skipped. The chain will be followed until a rejection handler is found.
it seems that the promise returned by SparkRequestService.submitRequest(request) is not rejected when you get the error inside resData. For this reason the successCallback of then is invoked and not the second one, the errorCallback.
For this reason, inside the successCallback you need to inspect the data of resData to check errors and behave accordingly, for example:
var getSubmit = function (request) {
return SparkRequestService
.submitRequest(request)
.then(function (resData) {
console.log("resData", resData);
if(resData === null) { // Check for errors
// There is an error, manage it inside this block of code
// ...
// You can also create a rejected promise with $q.reject() and passing resData containing the errors
return $q.reject(resData);
} else {
// Call resetEnrollment() in the ELSE branch so, it is executed only if resData does not contain errors
enrollmentService.resetEnrollment();
return resData;
}
},
function (resData) {
console.log('error');
}
);
};
I am using $http to make a call. Based on the successful result of the call I may decided to throw an error/reject and have it trickle down to the next call as an error. However if an error is thrown it just halt the process. How can I force the $http promise to reject without wrapping it in some $q code?
// A service
angular.module('app').factory('aService', function ($http, config) {
return {
subscribe: function (params) {
return $http({
url: '...'
method: 'JSONP'
}).then(function (res) {
// This is a successful http call but may be a failure as far as I am concerned so I want the calling code to treat it so.
if (res.data.result === 'error') throw new Error('Big Errror')
}, function (err) {
return err
})
}
}
})
// Controller
aService.subscribe({
'email': '...'
}).then(function (result) {
}, function (result) {
// I want this to be the Big Error message. How do I get here from the success call above?
})
In the above code I would like the Big Error message to end up as a rejected call. However in this case it just dies with the error. This is how I handle things in say Bluebird but it's a no go here.
Ti continue the Chain in a rejected state just return a rejected promise $q.reject('reason') from your $http result something like
$http.get(url).then(
function (response){
if(something){
return $q.reject('reason');
}
return response;
}
)
That way you'll get a a rejected promise and can react to it even when the api call is successful.
In all my services, I'm just invoking REST services and returning the promises to the controllers. All the error's are handled at controllers using catch like below,
MyService.getData(url).then(getDataSuccess).catch(exception.catcher('Contact Admin : '));
My question here is, Since the real $http calls will be made at service, should I have to write catchers in service or catching in controller is fine?,
Scenario 1:
function getData(url){
return $http.get(url);
}
Scenario 2: (Nested calls to make combined results)
function getOtherData(url){
var defer = $q.defer();
$http.get(url).then(
function(response){
$http.get(nextService).then(
function(res){
defer.resolve('combined data');
}
)
}
);
return defer.promise;
}
Both the service method is not handling any errors. Instead it just returns the promise. Will there be any situation where this kind of exception handling will get failed?
Note: I have created decorators for handling javascript,angular errors and route errors separately. This question is particularly about $http service errors.
Yes what you have can fail triggering your catch because you have no reject().
You are using an anti-pattern creating your own promise and not chaining the nested request properly. Neither of those request rejections will be returned anywhere.
To be able to chain these get rid of the $q.defer() and do:
function getOtherData(url) {
// return beginning of promise chain
return $http.get(url).then(function (response) {
// return next promise
return $http.get(nextService).then(function (res) {
// combine and return the data
return {
d1 : response.data,
d2 : res.data
};
});
});
}
Now walk through the scenarios and each part of chain is intact.
Think of the chain as each then needs a return until the end of the chain
Scenario 2: (Nested calls to make combined results)
Failed Scenario
function getOtherData(url){
var defer = $q.defer();
$http.get(url).then(
function(response){
$http.get(nextService).then(
function(res){
defer.resolve('combined data');
}
)
}
);
return defer.promise;
}
This scenario will fail if the first $http.get has an error. The promise will hang and never get resolved. This is why we recommend avoiding using $q.defer to create promises from services that already return promises.
Instead return data and chain promises.
function getOtherData(url) {
var promise = $http.get(url);
var derivedPromise =
promise.then ( function (response) {
var data = response.data;
var nextPromise = $http.get(nextService);
var derivedNext = nextPromise.then(function(response) {
//return for chaining
return response.data;
});
//return composite for chaining
return $q.all([data, derivedNext]);
});
return derivedPromise;
};
The getOtherData(url) promise will be fulfilled with an array with the data from the two XHRs or it will be rejected with the first error response.
It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.1
Chaining error handlers
In an error handler, to convert a rejected resolution to a fulfilled resolution return data. To chain a rejection, throw the error.
For example:
promise = http.get(someUrl);
derivedPromise = promise.catch(function(errorResponse) {
if (fixable) {
fixedPromise = $http.get(fixedUrl);
//return to convert
return fixedPromise;
} else {
//throw to chain rejection
throw errorResponse;
}
};
By chaining error handlers, errors can be handled both by the service and the client of the service.
This makes it possible to implement powerful APIs like $http's response interceptors.1
Building on #georgeawg's answer, if you want to return multiple sets of data then you don't need nested calls.
function getOtherData(url) {
var promise1 = $http.get(url).then ( function (response) {
return response.data;
});
var promise2 = $http.get(nextService).then(function(response) {
return response.data;
});
return $q.all([promise1, promise2]);
};
Now the caller gets a promise that resolves to a list of the 2 data items (or is rejected if either request fails). The only real difference is that both requests are issues in parallel.
This generalises easily to a situation where you could have a list of urls, fetch them all in parallel and get an array of the response.data items.
Because you get back only a single promise that resolves to an array of data you can handle the result in the controller, but you only need one error handler.
MyService.getOtherData(url)
.then(getDataSuccess)
.catch(exception.catcher('Contact Admin : '));
Although the original question doesn't specify, it might be the case that the second url depends on the result from the first. You can handle that case here as well if you remember that you can call .then() multiple times on the same promise:
function getOtherData(url) {
var promise1 = $http.get(url).then ( function (response) {
return response.data;
});
var promise2 = promise1.then(function(response) {
// compute nextService from response.data here...
var nextService = foo(response.data);
return $http.get(nextService).then(function(response) {
return response.data;
});
});
return $q.all([promise1, promise2]);
};
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 ?
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.
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.
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]);
I'm a bit confused how to chain promises in AngularJS. I have a $resource implementation... This is some simplified code to get my point:
myResource
.save({ id: 123 })
.$promise
.then(function(data) {
// redirect to root on success
$location.url("/");
})
.catch(function() {
console.log("Failed");
})
.finally(function() {
// reenable form
$scope.enabled = true;
})
What I'm confused about is whether my .catch() function will be called when my resource will fail saving or will it only catch errors that would originate in .then() function or maybe both?
When chaining these how can I have a single .catch (before .finally) that would catch all errors of calls that happened in the chain proceeding it?
Note: I'm aware I could provide the success and error functions in .save function but I have other parts to handle as well.
First of all, read the docs of Angular's implementation of promises.
Promises can be resolved in two ways - either successful (the resolve method) or not (the reject method).
When resource could not be saved (i.e. server did not respond with 2XX status), then your catch method will be called, as the promise will be rejected.
Also please note, that your catch handler should reject the promise for next possible handlers. When you return non-rejected value from your catch method, it is assumed that the error has been handled and the promise can call subsequent then methods, if any. Therefore, if there is any possibility that the promise is used somewhere else, your code should look like this:
// inject $q
myResource
.save({ id: 123 })
.$promise
.then(function(data) {
// redirect to root on success
$location.url("/");
})
.catch(function() {
console.log("Failed");
return $q.reject();
})
.finally(function() {
// reenable form
$scope.enabled = true;
})
Also notice that you made a typo in the then method - you wrote "ulr" instead of "url".