Reject from 'response' into 'responseError' - angularjs

Sometimes, the API I'm using will return 200 ok even though there has been an error. The response JSON object will look something like:
{
error: true
}
I've built a $http response interceptor that simply checks for this error and rejects it. I want it to then jump into my responseError function:
$httpProvider.interceptors.push(function($q) {
return {
response: function (response) {
if (response.data.error) {
// There has been an error, reject this response
return $q.reject(response);
}
return response;
},
responseError: function(rejection) {
// Display an error message to the user
...
return $q.reject(rejection);
}
}
});
Problem is, even after rejecting the response, my responseError function isn't called. It is called on 500 errors etc so I know it's working. I'd expect a rejection to do the same thing.
From the docs:
responseError: interceptor gets called when a previous interceptor threw an error or resolved with a rejection.
Any ideas on what's missing?

Looks like this isn't possible to do. To cut down on duplicate code, simply declare the error handling function separately and reuse it inside the response and responseError functions.
$httpProvider.interceptors.push(function($q) {
var handleError = function (rejection) { ... }
return {
response: function (response) {
if (response.data.error) {
return handleError(response);
}
return response;
},
responseError: handleError
}
});

To add to this answer: rejecting the promise in the response interceptor DOES do something.
Although one would expect it to call the responseError in first glance, this would not make a lot of sense: the request is fulfilled with succes.
But rejecting it in the response interceptor will make the caller of the promise go into error handling.
So when doing this
$http.get('some_url')
.then(succes)
.catch(err)
Rejecting the promise will call the catch function. So you don't have you proper generic error handling, but your promise IS rejected, and that's useful :-)

Should you want to pass the http response to the responseError handler, you could do it like this:
$httpProvider.interceptors.push(function($q) {
var self = {
response: function (response) {
if (response.data.error) {
return self.responseError(response);
}
return response;
},
responseError: function(response) {
// ... do things with the response
}
}
return self;
});

Related

Any way to add local interceptor (only for one request)

Is there a way to add interceptor only specific to one particular request, like transformRespponse?
Doing like so will add global interceptor that will be executed on every request.
$httpProvider.interceptors.push(function($q) {
return {
'response': function(response) {
if (isBad(response)) {
return $q.reject(rejectionReason(response));
}
}
};
});
I think you over engineer the solution. $http already return promise with two callbacks
$http.get('your_url').then(onSuccess, onError)
function onSuccess (response) {
if (isBad(response)) {
return $q.reject(rejectionReason(response));
}
// do some magic
}
function onError(response){
return $q.reject(rejectionReason(response))
}
and really the isBad should probably happen on server side and return an http error so it's handled by onError
you can get request url inside response function
$httpProvider.interceptors.push(function($q) {
return {
'response': function(response) {
if(response.config.url == '/particular_request_url'){
//do something
}
return response
}
};
});

Force rejecting Angular $http call

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.

ionic http request loading timeout

I am using the following to show a loading screen whenever I am performing a http request however sometimes if there is an error then it will stay loading (because of the backdrop the app becomes unusable). Rather than hide it on every error checker I was wondering if it is possible to call the timeout after 5 seconds?
.config(function($httpProvider) {
$httpProvider.defaults.timeout = 5000;
$httpProvider.interceptors.push(function($rootScope) {
return {
request: function(config) {
$rootScope.$broadcast('loading:show')
return config
},
response: function(response) {
$rootScope.$broadcast('loading:hide')
return response
}
}
})
})
Following Jess's answer it now looks like this :
.config(function($httpProvider) {
$httpProvider.defaults.timeout = 5000;
$httpProvider.interceptors.push(function($rootScope) {
return {
request: function(config) {
$rootScope.$broadcast('loading:show')
return config
},
response: function(response) {
$rootScope.$broadcast('loading:hide')
return response
},
responseError: function(response) {
$rootScope.$broadcast('loading:hide')
return response
},
requestError: function(response) {
$rootScope.$broadcast('loading:hide')
return response
}
}
})
})
However I cannot seem to be able to put an alert in the requestError to inform the user.
Question
How can I implement an alert to notify the user of the error that has occurred?
try adding responseError and requestError so like this:
responseError: function(responseError) {
$rootScope.$broadcast('loading:hide')
return responseError
and do this again with requestError,
This is from the angular http interceptors docs
requestError: interceptor gets called when a previous interceptor threw an error or resolved with a rejection.
responseError: interceptor gets called when a previous interceptor threw an error or resolved with a rejection.
Edit to answer comment:
so if you want to throw a alert on responseError than a add a $rootScope.$broadcast('response:error')
in the responseError function
then in the controller you want to throw the alert in just do a
$scope.$on('response:error', function(){throw the error here});
you can also do the same for requestError
this works because $broadcast -- dispatches the event downwards to all child scopes

chaining ngResource $promise success and errors

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]);

AngularJS ngResource: How to always use the same catch function?

Every time I hit the servers using any $resource I want to show the same alert to my users whenever it fails.
Today, it looks like:
function tryAgain() { alert("try again") }
myResource.query().$promise.catch(tryAgain);
myResource.update(...).$promise.catch(tryAgain);
myResource.delete(...).$promise.catch(tryAgain);
otherResource.query().$promise.catch(tryAgain);
Is there a way to configure the default error handling function for ngResource? I'm looking for something like:
$resource.somethingMagicHere.defaults.catch(tryAgain);
You can use an interceptor in your app.config() section. This will catch all response errors originating from $http which $resource uses.
$httpProvider.interceptors.push(function($q) {
return {
'responseError': function(response) {
if (response.status == 401) {
// Handle 401 error code
}
if (response.status == 500) {
// Handle 500 error code
}
// Always reject (or resolve) the deferred you're given
return $q.reject(response);
}
};
});
The #kba answer helped me find the path. The following article made me understand it:
http://www.webdeveasy.com/interceptors-in-angularjs-and-useful-examples/
Just declare it once and reuse:
var defaultErrorHandler = function() {
alert("try again")
}
myResource.query(...).$promise.catch(defaultErrorHandler());
myResource.update(...).$promise.catch(defaultErrorHandler());
...

Resources