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.
Related
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.
This question already has answers here:
Is this a "Deferred Antipattern"?
(3 answers)
Closed 5 years ago.
I create $http service and using $q.
Here is my $http service:
function dashboardService($http, $log, $q, config) {
var service = {
getClientId: getClientIDByLayout,
};
return service;
function getClientIDByLayout(layoutId) {
var deferred = $q.defer();
return $http.get(config.baseUrl + "api/ClientLayoutMercator?layoutId=" + layoutId).then(function (result) {
deferred.resolve(result.data);
}, function (result) {
deferred.reject(result);
});
return deferred.promise;
}
}
And here is how I call service above inside controller:
dashboardService.getClientId(layoutId).then(function (data) {
var t = data;//undifined
});
But result I get in this row var t = data is undefined.
Any idea why I get undefined from the service?
Basically you have two return statement inside your getClientIDByLayout function and both are returning promise itself. As I can see with your current implementation your're creating new promise & managing rejection / resolve manually. But the problem is the 1st return statement (return $http.get() is making other return statement(return deferred.promise) redundant. Hence 1st promise returned to subscription from controller. Eventually $http.get doesn't return anything so you get undefined in successCallback of then.
You can easily fix this issue by removing 1st return statement as shown below.
function getClientIDByLayout(layoutId) {
var deferred = $q.defer();
//removed `return` from below code.
$http.get(config.baseUrl + "api/ClientLayoutMercator?layoutId=" + layoutId).then(function (result) {
deferred.resolve(result.data);
}, function (result) {
deferred.reject(result);
});
//custom promise should get return
return deferred.promise;
}
Ideally creating promise overhead considered as antipattern, rather you can utilize the promise returned by $http.get. Just return a data from its success callback to chain the promise.
Code
function getClientIDByLayout(layoutId) {
̶v̶a̶r̶ ̶d̶e̶f̶e̶r̶r̶e̶d̶ ̶=̶ ̶$̶q̶.̶d̶e̶f̶e̶r̶(̶)̶;̶
return $http.get(config.baseUrl + "api/ClientLayoutMercator?layoutId=" + layoutId)
.then(function (result) {
//returning data from promise, it will provide it to subsequent `.then`
return result.data;
}, function (error) {
͟r͟e͟t͟u͟r͟n͟ $q.reject(error);
}
);
}
Instead of using $q.defer, simply return or throw to the handler functions in the .then method:
function dashboardService($http, $log, ̶$̶q̶,̶ config) {
var service = {
getClientId: getClientIDByLayout,
};
return service;
function getClientIDByLayout(layoutId) {
̶v̶a̶r̶ ̶d̶e̶f̶e̶r̶r̶e̶d̶ ̶=̶ ̶$̶q̶.̶d̶e̶f̶e̶r̶(̶)̶;̶
return $http.get(config.baseUrl + "api/ClientLayoutMercator?layoutId=" + layoutId).then(function (result) {
̶d̶e̶f̶e̶r̶r̶e̶d̶.̶r̶e̶s̶o̶l̶v̶e̶(̶r̶e̶s̶u̶l̶t̶.̶d̶a̶t̶a̶)̶;̶
return result.data;
}, function (result) {
̶d̶e̶f̶e̶r̶r̶e̶d̶.̶r̶e̶j̶e̶c̶t̶(̶r̶e̶s̶u̶l̶t̶)̶;̶
throw result;
});
̶r̶e̶t̶u̶r̶n̶ ̶d̶e̶f̶e̶r̶r̶e̶d̶.̶p̶r̶o̶m̶i̶s̶e̶;̶
}
}
The .then method returns a new promise which is resolved or rejected via the return value of the successCallback or errorCallback (unless that value is a promise, in which case it is resolved with the value which is resolved in that promise using promise chaining).1
By erroneously returning a promise with a then method that contained functions that lacked return or throw statements the service was returning a promise that resolved as undefined.
For more information, see You're Missing the Point of Promises.
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.
I have a state defined like this:
.state('list', {
url: '/list',
controller: 'ctrl',
resolve: {
data: ['DataService', function(DataService) {
return DataService.getList();
}]
}
})
The getList of DataService makes the http request:
var httpRequest = $http(categoryRequest);
httpRequest.then(function (response) {
return response.data;
})
.catch(function (error) {
console.log('could not get categories from server');
});
return httpRequest;
In controller I just assign the list to its list property:
function ctrl(data) {
this.list = data.data;
}
The problem:
No matter what I return in success callback of http request, I always get the whole response in resolve of state provider.
So I have to do data.data in controller to get the data from response.
Questions:
Is my assumption true that I will always get the whole reponse in resolve?
How to get just the data form response that I do not have to get it in controller.
Best regards,
var httpRequest = $http(categoryRequest);
So httpRequest is a Promise<Response>
httpRequest.then(function (response) {
This creates another promise, but this new promise is not assigned to anything.
return httpRequest;
This returns the original Promise<Response>.
You want
httpRequest = httpRequest.then(function (response) {
Or simply
return httpRequest.then(function (response) {
So that what you return is the new promise.
To give you a simpler analog example, your code is similar to
var a = 1;
a + 1;
return a;
That returns 1, not 2.
To return 2, you need
var a = 1;
a = a + 1;
return a;
or
var a = 1;
return a + 1;
There are two problems. First, the service is returning the original httpPromise and not the promise derived from the original promise. Second, the error handler is converting the rejection to a success.
var httpRequest = $http(categoryRequest);
//httpRequest.then(function (response) {
var derivedPromise = httpRequest.then(function onSuccess(response) {
//return to chain data
return response.data;
}).catch(function onReject(error) {
console.log('could not get categories from server');
//IMPORTANT to avoid conversion
throw error;
//OR
//return $q.reject(error);
});
//return httpRequest;
return derivedPromise;
The .then method of a promise returns a new promise derived from the original promise. It does not mutate the original promise.
A common problem is the omission of a throw or return $q.reject statement from a rejection handler. Functions without such statements return a value of undefined which will convert a rejection to a success which resolves as undefined.
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.