Angular promise - provide catch function - angularjs

I am trying to come up with a generic service that would run my http requests and provides placeholders for success and error functions. Here is my code:
var deferred = $q.defer();
var response = $http({
..............
});
response.then(function (data) {
deferred.resolve(data.data);
});
response.catch(function (data) {
alert('Error');
});
And in Controller:
service.executeHTTPCall('parameters').then(successHandler);
My question is how do I provide an error handler for an executeHTTPCall call?
Thanks

Well, avoid the explicit construction and things fall into place:
// no deferred, just return
return $http({..... }).then(function(data){
// handle successes
return data.data;
}, function(err){ // handle failures here
alert("ERrror!!!"); // logic here
return $q.reject(err); // this is important, like a `catch and throw`
// to signal we're still handling errors.
});
But, may I suggest http interceptors instead?
$httpProvider.interceptors.push(function($q, dependency1, dependency2) {
return {
'response': function(response) {
// do something on success
},
'responseError': function(rejection) {
// do something on failure, see docs for more usage examples here
};
});
That would mean you can avoid wrapping every $http promise manually and also play nicer with tests.

Related

Type IHttpPromiseCallbackArg<{}> is not assignable to type 'string' [duplicate]

I'm finding it hard to understand the "deferred antipattern". I think I understand it in principal but I haven't seen a super simple example of what a service, with a differed promise and one with antipattern, so I figured I'd try and make my own but seeing as how I'm not super in the know about it I'd get some clarification first.
I have the below in a factory (SomeFactory):
//url = 'data.json';
return {
getData: function(){
var deferred = $q.defer();
$http.get(destinationFactory.url)
.then(function (response) {
if (typeof response.data === 'object') {
deferred.resolve(response.data);
} else {
return deferred.reject(response.data);
}
})
.catch(function (error) {
deferred.reject(error);
});
return deferred.promise;
}
The reason I am checking its an object is just to add a simple layer of validation onto the $http.get()
And below, in my directive:
this.var = SomeFactory.getData()
.then(function(response) {
//some variable = response;
})
.catch(function(response) {
//Do error handling here
});
Now to my uderstanding, this is an antipattern. Because the original deferred promise catches the error and simply swallows it. It doesn't return the error so when this "getData" method is called I have do another catch to grab the error.
If this is NOT an antipattern, then can someone explain why both require a "callback" of sorts? When I first started writing this factory/directive I anticipated having to do a deffered promise somewhere, but I didn't anticipate having to .catch() on both sides (aka I was sort of thinking I could get the factory to return the response or the error if I did a SomeFactory.getData()
Is this a “Deferred Antipattern”?
Yes, it is. 'Deferred anti-pattern' happens when a new redundant deferred object is created to be resolved from inside a promise chain. In your case you are using $q to return a promise for something that implicitly returns a promise. You already have a Promise object($http service itself returns a promise), so you just need to return it!
Here's the super simple example of what a service, with a deferred promise and one with antipattern look like,
This is anti-pattern
app.factory("SomeFactory",['$http','$q']){
return {
getData: function(){
var deferred = $q.defer();
$http.get(destinationFactory.url)
.then(function (response) {
deferred.resolve(response.data);
})
.catch(function (error) {
deferred.reject(error);
});
return deferred.promise;
}
}
}])
This is what you should do
app.factory("SomeFactory",['$http']){
return {
getData: function(){
//$http itself returns a promise
return $http.get(destinationFactory.url);
}
}
while both of them are consumed in the same way.
this.var = SomeFactory.getData()
.then(function(response) {
//some variable = response;
},function(response) {
//Do error handling here
});
There's nothing wrong with either examples(atleast syntactically)..but first one is redundant..and not needed!
Hope it helps :)
I would say that it is the classic deferred anti-pattern because you are creating needless deferred objects. However, you are adding some value to the chain (with your validation). Typically, IMO, the anti-pattern is particularly bad when deferred objects are created for very little or no benefit.
So, the code could be much simpler.
$q promises have a little documented feature of automatically wrapping anything returned inside a promise in a promise (using $q.when). In most cases this means that you shouldn't have to manually create a deferred:
var deferred = $q.defer();
However, that is how the documentation demonstrates how to use promises with $q.
So, you can change your code to this:
return {
getData: function(){
return $http.get(destinationFactory.url)
.then(function (response) {
if (typeof response.data === 'object') {
return response.data;
} else {
throw new Error('Error message here');
}
});
// no need to catch and just re-throw
});
}
Using the $q constructor is a deferred anti-pattern
ANTI-PATTERN
vm.download = function() {
var url = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf";
return $q(function(resolve, reject) {
var req = {
method: 'POST',
url: url,
responseType: 'arraybuffer'
};
$http(req).then(function(response) {
resolve(response.data);
}, function(error) {
reject(error);
});
});
}
CORRECT
vm.download = function() {
var url = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf";
var req = {
method: 'POST',
url: url,
responseType: 'arraybuffer'
};
return $http(req).then(function(response) {
return response.data;
});
}
The $http service already returns a promise. Using the $q constructor is unnecessary and error prone.

Return chain of promises in AngularJS [duplicate]

I'm finding it hard to understand the "deferred antipattern". I think I understand it in principal but I haven't seen a super simple example of what a service, with a differed promise and one with antipattern, so I figured I'd try and make my own but seeing as how I'm not super in the know about it I'd get some clarification first.
I have the below in a factory (SomeFactory):
//url = 'data.json';
return {
getData: function(){
var deferred = $q.defer();
$http.get(destinationFactory.url)
.then(function (response) {
if (typeof response.data === 'object') {
deferred.resolve(response.data);
} else {
return deferred.reject(response.data);
}
})
.catch(function (error) {
deferred.reject(error);
});
return deferred.promise;
}
The reason I am checking its an object is just to add a simple layer of validation onto the $http.get()
And below, in my directive:
this.var = SomeFactory.getData()
.then(function(response) {
//some variable = response;
})
.catch(function(response) {
//Do error handling here
});
Now to my uderstanding, this is an antipattern. Because the original deferred promise catches the error and simply swallows it. It doesn't return the error so when this "getData" method is called I have do another catch to grab the error.
If this is NOT an antipattern, then can someone explain why both require a "callback" of sorts? When I first started writing this factory/directive I anticipated having to do a deffered promise somewhere, but I didn't anticipate having to .catch() on both sides (aka I was sort of thinking I could get the factory to return the response or the error if I did a SomeFactory.getData()
Is this a “Deferred Antipattern”?
Yes, it is. 'Deferred anti-pattern' happens when a new redundant deferred object is created to be resolved from inside a promise chain. In your case you are using $q to return a promise for something that implicitly returns a promise. You already have a Promise object($http service itself returns a promise), so you just need to return it!
Here's the super simple example of what a service, with a deferred promise and one with antipattern look like,
This is anti-pattern
app.factory("SomeFactory",['$http','$q']){
return {
getData: function(){
var deferred = $q.defer();
$http.get(destinationFactory.url)
.then(function (response) {
deferred.resolve(response.data);
})
.catch(function (error) {
deferred.reject(error);
});
return deferred.promise;
}
}
}])
This is what you should do
app.factory("SomeFactory",['$http']){
return {
getData: function(){
//$http itself returns a promise
return $http.get(destinationFactory.url);
}
}
while both of them are consumed in the same way.
this.var = SomeFactory.getData()
.then(function(response) {
//some variable = response;
},function(response) {
//Do error handling here
});
There's nothing wrong with either examples(atleast syntactically)..but first one is redundant..and not needed!
Hope it helps :)
I would say that it is the classic deferred anti-pattern because you are creating needless deferred objects. However, you are adding some value to the chain (with your validation). Typically, IMO, the anti-pattern is particularly bad when deferred objects are created for very little or no benefit.
So, the code could be much simpler.
$q promises have a little documented feature of automatically wrapping anything returned inside a promise in a promise (using $q.when). In most cases this means that you shouldn't have to manually create a deferred:
var deferred = $q.defer();
However, that is how the documentation demonstrates how to use promises with $q.
So, you can change your code to this:
return {
getData: function(){
return $http.get(destinationFactory.url)
.then(function (response) {
if (typeof response.data === 'object') {
return response.data;
} else {
throw new Error('Error message here');
}
});
// no need to catch and just re-throw
});
}
Using the $q constructor is a deferred anti-pattern
ANTI-PATTERN
vm.download = function() {
var url = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf";
return $q(function(resolve, reject) {
var req = {
method: 'POST',
url: url,
responseType: 'arraybuffer'
};
$http(req).then(function(response) {
resolve(response.data);
}, function(error) {
reject(error);
});
});
}
CORRECT
vm.download = function() {
var url = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf";
var req = {
method: 'POST',
url: url,
responseType: 'arraybuffer'
};
return $http(req).then(function(response) {
return response.data;
});
}
The $http service already returns a promise. Using the $q constructor is unnecessary and error prone.

Error handling in Angularjs for deferred promise

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.

Stop request in angularjs interceptor

How can I stop a request in Angularjs interceptor.
Is there any way to do that?
I tried using promises and sending reject instead of resolve !
.factory('connectionInterceptor', ['$q', '$timeout',
function($q, $timeout) {
var connectionInterceptor = {
request: function(config) {
var q = $q.defer();
$timeout(function() {
q.reject();
}, 2000)
return q.promise;
// return config;
}
}
return connectionInterceptor;
}
])
.config(function($httpProvider) {
$httpProvider.interceptors.push('connectionInterceptor');
});
I ended up bypassing angular XHR call with the following angular Interceptor:
function HttpSessionExpiredInterceptor(sessionService) {
return {
request: function(config) {
if (sessionService.hasExpired()) {
/* Avoid any other XHR call. Trick angular into thinking it's a GET request.
* This way the caching mechanism can kick in and bypass the XHR call.
* We return an empty response because, at this point, we do not care about the
* behaviour of the app. */
if (_.startsWith(config.url, '/your-app-base-path/')) {
config.method = 'GET';
config.cache = {
get: function() {
return null;
}
};
}
}
return config;
}
};
}
This way, any request, POST, PUT, ... is transformed as a GET so that the caching mechanism can be
used by angular. At this point, you can use your own caching mechanism, in my case, when session
expires, I do not care anymore about what to return.
The $http service has an options
timeout to do the job.
you can do like:
angular.module('myApp')
.factory('httpInterceptor', ['$q', '$location',function ($q, $location) {
var canceller = $q.defer();
return {
'request': function(config) {
// promise that should abort the request when resolved.
config.timeout = canceller.promise;
return config;
},
'response': function(response) {
return response;
},
'responseError': function(rejection) {
if (rejection.status === 401) {
canceller.resolve('Unauthorized');
$location.url('/user/signin');
}
if (rejection.status === 403) {
canceller.resolve('Forbidden');
$location.url('/');
}
return $q.reject(rejection);
}
};
}
])
//Http Intercpetor to check auth failures for xhr requests
.config(['$httpProvider',function($httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
}]);
Not sure if it is possible in general. But you can start a $http request with a "canceler".
Here is an example from this answer:
var canceler = $q.defer();
$http.get('/someUrl', {timeout: canceler.promise}).success(successCallback);
// later...
canceler.resolve(); // Aborts the $http request if it isn't finished.
So if you have control over the way that you start your request, this might be an option.
I just ended up in returning as an empty object
'request': function request(config) {
if(shouldCancelThisRequest){
return {};
}
return config;
}
Here is what works for me, especially for the purposes of stopping the outgoing request, and mocking the data:
app
.factory("connectionInterceptor", [
"$q",
function ($q) {
return {
request: function (config) {
// you can intercept a url here with (config.url == 'https://etc...') or regex or use other conditions
if ("conditions met") {
config.method = "GET";
// this is simulating a cache object, or alternatively, you can use a real cache object and pre-register key-value pairs,
// you can then remove the if block above and rely on the cache (but your cache key has to be the exact url string with parameters)
config.cache = {
get: function (key) {
// because of how angularjs $http works, especially older versions, you need a wrapping array to get the data
// back properly to your methods (if your result data happens to be an array). Otherwise, if the result data is an object
// you can pass back that object here without any return codes, status, or headers.
return [200, mockDataResults, {}, "OK"];
},
};
}
return config;
},
};
},
])
.config(function ($httpProvider) {
$httpProvider.interceptors.push("connectionInterceptor");
});
If you are trying to mock a result like
[42, 122, 466]
you need to send an array with some http params back, its just how the ng sendReq() function is written unfortunately. (see line 1414 of https://github.com/angular/angular.js/blob/e41f018959934bfbf982ba996cd654b1fce88d43/src/ng/http.js#L1414 or snippet below)
// from AngularJS http.js
// serving from cache
if (isArray(cachedResp)) {
resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3], cachedResp[4]);
} else {
resolvePromise(cachedResp, 200, {}, 'OK', 'complete');
}

AngularJS global $http state ajax loader

I have an AngularJS app, and need an ajax loader for every request done by the $http - is there a simple way to do this.
My solution now is to set $rootScope.loading = 1 everytime i call a $http, and on the success set $rootScope.loading = 0..
What is the "best practice" for this?
My code now looks like:
$rootScope.loading = 1;
$http({method : "POST", url:url, data: utils.params(data), headers: {'Content-Type': 'application/x-www-form-urlencoded'}}).success(function() {
$rootScope.loading = 0;
});
In this case will be better to use interceptor
Anytime that we want to provide global functionality on all of our requests, such as authentication,
error handling, etc., it’s useful to be able to provide the ability to intercept all requests before they
pass to the server and back from the server.
angular.module('myApp')
.factory('myInterceptor',
function ($q,$rootScope) {
var interceptor = {
'request': function (config) {
$rootScope.loading = 1;
// Successful request method
return config; // or $q.when(config);
},
'response': function (response) {
$rootScope.loading = 0;
// successful response
return response; // or $q.when(config);
},
'requestError': function (rejection) {
// an error happened on the request
// if we can recover from the error
// we can return a new request
// or promise
return response; // or new promise
// Otherwise, we can reject the next
// by returning a rejection
// return $q.reject(rejection);
},
'responseError': function (rejection) {
// an error happened on the request
// if we can recover from the error
// we can return a new response
// or promise
return rejection; // or new promise
// Otherwise, we can reject the next
// by returning a rejection
// return $q.reject(rejection);
}
};
return interceptor;
});
and register it to the config
angular.module('myApp')
.config(function($httpProvider) {
$httpProvider.interceptors.push('myInterceptor');
});
example from ng-book
Use an http interceptor in order to intercept all your $http requests\responses and perform logic in them.
Here's an example of creating a custom one.
Here's an example of a ready module.

Resources