Here is my service's response:
response = response.then(function (data) {
return data.data;
});
response.catch(function (data) {
$q.reject(data);
});
// Return the promise to the controller
return response;
In Interceptor I am returning:
return $q.reject();
But, still I am getting back into:
response.then
Is possible to get back into the catch block?
Thanks
Adding more code:
.service('APIInterceptor', function ($q, $rootScope, UserService) {
var service = this;
service.request = function(config) {
return $q.reject();
//return config;
};
service.responseError = function (response) {
return response;
};
})
What happens is that your .request creates an error (by doing return $q.reject()), but your .responseError "handles" that error (by virtue of being there), thus resulting in the overall successful resolution.
Indeed, removing .responseError handler makes the error bubble up to .catch. Alternatively, you can also return $q.reject() in .responseError.
Related
I am trying to neatly manage service function calls from the controller while handling errors from the API.
However when I do the following, I still get this is showing even with 403 and 404 errors in my console even when the API throws back a 403 or 404.
I am assuming this could work if I added catchin my services file but I would prefer keep this managed from the controller. Is this possible?
Controller:
angular.module('EnterDataCtrl', []).controller('EnterDataController', ['$scope', 'Data', '$state', function ($scope, Data, $state) {
vm = this;
vm.getRules = function (e, rule_query, data) {
if (e.keyCode == 13) {
Data.getRules(rule_query,data).then(function (data) {
console.log('this is showing even with 403 and 404 errors');
}).catch(function(res) {
console.log(res);
});
}
}
}]);
Service:
angular.module('EnterDataService', []).factory('Data', ['$http', function ($http) {
return {
getRules: function getRules(rule_query,data) {
var apiBase = apiUrl + 'get-rules';
var config = {
handleError:true,
params: {
rule_query: rule_query,
data : data
}
};
return $http.get(apiBase, config).catch(function () {});
}
}
}]);
In your service, you are setting up the catch without doing anything. This in general is bad practice, if you're going to use a catch block, then handle the error there. If not, don't declare the catch at all.
You can either remove the catch completely from the service method, which should result in your controller's catch handling it.
OR, you can leave the catch in the service call, and make sure to throw so that the error correctly bubbles up.
When a rejection handler omits a throw statement, it returns undefined. This converts a rejected promise to a fulfilled promise that resolves to undefined.
To avoid unintended conversions, it is important to include a throw statement in rejection handlers.
app.service('Data', ['$http', function ($http) {
this.getRules = function getRules(rule_query,data) {
var apiBase = apiUrl + 'get-rules';
var config = {
handleError:true,
params: {
rule_query: rule_query,
data : data
}
};
//return $http.get(apiBase, config).catch(function () {});
return $http.get(apiBase, config)
.catch(function (errorResponse) {
console.error("Data service error");
//IMPORTANT
throw errorResponse;
});
}
}]);
I built a factory to return data that uses an HTTP Get through a deferred promise. It work great when it is the happy path and the url is correct. But when there is an error I would like to catch it. It seems that I am but a 500 error still shows in the console. Is there a way to catch this also? Also, I want to do processing on the reject I'm having trouble figuring out how to do that. TIA
angular.module("accQueries")
.factory('leaseFactory', ['$http', '$q', function ($http, $q) {
return {
leases: '',
makeRequest: function (url) {
// Create the deferred object
var deferred = $q.defer();
$http.get(url).then(function (resp) {
deferred.resolve(resp.data);
})
// potentially catch http error here??
.catch(function (err) {
deferred.reject(err);
console.log('rejected : ' + err );
console.dir(err);
this.leases = '';
});
return deferred.promise;
},
// Return a single lease based on lease number
getLease: function (pLeaseNum) {
this.leases = this.makeRequest("http://someserver/AccruentQA_DB/webresources/restfulservices.latbllease/leaseNumber/" + pLeaseNum);
// Return the lease object stored on the service
return this.leases;
},
// Return all leases based on lease name
getLeases: function () {
this.leases = this.makeRequest("http://someserver/AccruentQA_DB/webresources/restfulservices.latbllease/name/");
// Return the lease object stored on the service
return this.leases;
}
};
}]);
It is not needed to wrap a $http call in $q, because $http returns a promise itself. So just returning $http like this is sufficient:
makeRequest: function (url) {
return $http.get(url);
}
If you would want to chain do something in the makeRequest function with the answers be4 passing it on, you can chain promises like so:
makeRequest: function (url) {
return $http.get(url).then(function(response){
//do something
return response;
}, function(error){
//do something
return error;
});
}
There's no way to prevent the HTTP error from appearing in the console. The browser does that before it passes the results back to angular. However, an error causes the $http promise to be rejected, which means you can handle it using the optional second argument to then()
return $http.get('url').then(
function(response) {
this.leases = response.data;
},
function(response) {
var statusCode = response.status;
var response = response.data;
// other error processing
this.leases = '';
}
}).then(function() { return this.leases; }
You can do various things depending on the status code and response data. If your server emits an error 500, that's what response.status will be. Timeouts have a status of 0.
You should also be aware that getLease() will return before the ajax request is complete. You should return the promise, and then in the calling code, chain another then() to do something once the promise is resolved.
The past view days I read a lot of best practices in handling with promises. One central point of the most postings where something like this:
So if you are writing that word [deferred] in your code
[...], you are doing something wrong.1
During experimenting with the error handling I saw an for me unexpected behavior. When I chain the promises and It run into the first catch block the second promise gets resolved and not rejected.
Questions
Is this a normal behavior in other libs / standards (e.g. q, es6), too and a caught error counts as solved like in try / catch?
How to reject the promise in the catch block so that the second gets, called with the same error / response object?
Example
In this example you see 'I am here but It was an error'
Full Plunker
function BaseService($http, $q) {
this.$http = $http;
this.$q = $q;
}
BaseService.prototype.doRequest = function doRequest() {
return this.$http({
method: 'GET',
url: 'not/exisint/url'
})
.then(function (response) {
// do some basic stuff
})
.catch(function(response) {
// do some baisc stuff e.g. hide spinner
});
}
function ChildService($http, $q) {
this.$http = $http;
this.$q = $q;
}
ChildService.prototype = Object.create(BaseService.prototype);
ChildService.prototype.specialRequest = function specialRequest() {
return this.doRequest()
.then(function (response) {
alert('I am here but It was an error');
})
.catch(function (response) {
// do some more specific stuff here and
// provide e.g. error message
alert('I am here but It was an error');
return response;
});
}
Workaround:
With this workaround you can solve this problem, but you have to create a new defer.
BaseService.prototype.doRequest = function doRequest() {
var dfd = this.$q.defer();
return this.$http({
method: 'GET',
url: 'not/exisint/url'
})
.then(function (response) {
// do some basic stuff
dfd.resolve(response);
})
.catch(function(response) {
// do some basic stuff e.g. hide spinner
dfd.reject(error);
});
}
Your workaround is almost correct, you can simplify it to the following:
BaseService.prototype.doRequest = function doRequest() {
return this.$http({
method: 'GET',
url: 'not/exisint/url'
})
.then(function (response) {
// do some basic stuff
return response;
}, function (error) {
return this.$q.reject(error);
});
}
$q.reject is a shortcut to create a deferred that immediately get's rejected.
Yes, this is default behaviour in other libraries as well. .then or .catch simply wraps the return value into a new promise. You can return a rejected promise to make the .catch chain work.
You can also do the opposite, for instance when you want to reject the promise in the success callback for whatever reason:
function getData() {
return this.$http.get(endpoint).then(result => {
// when result is invalid for whatever reason
if (result === invalid) {
return this.$q.reject(result);
}
return result;
}, err => this.$q.reject(err));
}
getData().then(result => {
// skipped
}, error => {
// called
});
See example above
Just to add to Dieterg's answer and to your workaround, you can also wrap the code into $q constructor:
BaseService.prototype.doRequest = function doRequest() {
return $q(function (resolve, reject) {
$http.get('not/exisint/url').then(function (response) { // success
/* do stuff */
resolve(response);
}, function (error) { // failure
/* do stuff */
reject(error);
});
});
};
I have multiple controllers on a page that use the same service, for the sake of example we will call the service USER.
The first time the USER.getUser() is called it does an $http request to GET data on the user. After the call is completed it stores the data in USER.data. If another call is made to USER.getUser() it checks if there is data in USER.data and if there is data it returns that instead of making the call.
My problem is that the calls to USER.getUser() happen so quickly that USER.data does not have any data so it fires the $http call again.
Here is what I have for the user factory right now:
.factory("user", function($http, $q){
return {
getUser: function(){
var that = this;
var deferred = $q.defer();
if(that.data){
deferred.resolve(that.data);
} else {
$http.get("/my/url")
.success(function(res){
that.data = res;
deferred.resolve(that.data);
});
}
return deferred.promise;
}
}
});
I hope my question makes sense. Any help would be much appreciated.
Does this work for you?
.factory("user", function($http, $q) {
var userPromise;
return {
getUser: function () {
if (userPromise) {
return userPromise;
}
userPromise = $http
.get("/my/url")
.then(function(res) {
return res.data;
});
return userPromise;
}
}
})
$http already returns the promise for you, even more, in 2 useful types: success & error. So basically, the option that #kfis offered does NOT catch errors and not flexible.
You could write something simple:
.factory('user', function($http) {
return {
getUser: function() {
$http.get('/my/url/')
.success(function(data) {
return data;
})
.error(function(err) {
// error handler
})
}
}
})
I've got this interceptor setup to read XML I receive on all my requests:
https://gist.github.com/SantechDev/539a70208d23d8918ce0
Now when the server returns a 500 error, it doesn't seem like the response goes through the interceptor. I tried logging response but nothing comes up
Would anyone know why?
I don't know how yours has to work but the ones I wrote look totally different..
var interceptor = ['$rootScope', '$q', "Base64", function (scope, $q, Base64) {
function success(response) {
return response;
}
function error(response) {
var status = response.status;
if (status == 401) {
window.location = "/account/login?redirectUrl=" + Base64.encode(document.URL);
return;
}
// otherwise
return $q.reject(response);
}
return function (promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);
you can check out the full code here.