AngularJS function returning promise with required parameter - angularjs

Simple question.
I have build this function:
// Gets the kit by id or slug
var _getKit = function (id) {
// Try to get our kit using our shared service
return sharedKitService.get(id).then(function (response) {
// Assign the response to our service
service.models.kit = response;
// Return our response
return response;
})
};
and I want to add a check to the function to be something like this:
// Gets the kit by id or slug
var _getKit = function (id) {
// If we have no id, exit the function
if (!id)
return;
// Try to get our kit using our shared service
return sharedKitService.get(id).then(function (response) {
// Assign the response to our service
service.models.kit = response;
// Return our response
return response;
})
};
But I know that won't work because if there is no id then the function will no longer produce a promise.
I know I could do something like this:
// Gets the kit by id or slug
var _getKit = function (id) {
// If we have no id
if (!id) {
// Defer our promise
var deferred = $q.derfer();
// Reject our promise
deferred.reject();
// Return our promise
return deferred.promise;
}
// Try to get our kit using our shared service
return sharedKitService.get(id).then(function (response) {
// Assign the response to our service
service.models.kit = response;
// Return our response
return response;
})
};
but this seems like overkill.
Is there an easier way to do this?

From the Angular docs:
Just return $q.reject(reason).
This returns Promise which is instantly rejected

you can simply use
$q.reject();

You can write your function as below for shorter purpose.
// Gets the kit by id or slug
var _getKit = function (id) {
var deferred = $q.defer();
// If we have no id
if (!id) {
// Reject our promise
deferred.reject();
} else {
sharedKitService.get(id).then(function (response) {
// Assign the response to our service
service.models.kit = response;
// Return our response
deferred.resolve(response);
});
}
return deferred.promise;
};

Related

angularjs wait for response from $http

I have the problem that my function doesn't wait for the response of http request and go further. I know that I can use promise to wait but I don't understand the concept.
I have a data service that have all http request :
function GetGroupIdFromBakery(bakeryId, successCallback, errorCallback) {
$http.get(service.baseUrl + "BakeriesGroup/Bakeries/" + bakeryId)
.then(function (result) { successCallback(result.data); }, errorCallback);
}
From another service, I call the data service :
var hasPermission = function (permission, params) {
permissionRoute = permission;
setIdEntity(params);
for (var i = 0; i < permissions.length; i++) {
if (permissionRoute.Name === permissions[i].Name) {
if (permissions[i].Scope == "System")
return true;
else if (permissions[i].Scope == permissionRoute.Scope && permissions[i].IdEntity == permissionRoute.IdEntity)
return true;
}
}
return false;
}
var setIdEntity = function (params) {
if (permissionRoute.Scope == "Bakery")
permissionRoute.IdEntity = parseInt(params.bakeryId);
else if (permissionRoute.Scope == "Group") {
if (params.bakeriesGroupId)
permissionRoute.IdEntity = parseInt(params.bakeriesGroupId);
else {
getGroupOfBakery(parseInt(params.bakeryId));
}
console.log(permissionRoute.IdEntity);
}
}
var getGroupOfBakery = function (bakeryId) {
DataService.GetGroupIdFromBakery(bakeryId, function (groupId) {
permissionRoute.IdEntity = groupId;
}, function (error) {
console.error("something went wrong while getting bakery");
alert("Une erreur s'est produite lors de la récupération de la boulangerie");
});
}
I must wait for the response of DataService.GetGroupIdFromBakery(). With this code, permission.EntityId is undefined when I call getGroupByBakery().
Can somebody help me, please?
You can add a watcher to your response data. I think it is EntityId in your case.
It get executed as soon as your EntityId changes. After getting the response data you can call the function, this time EntityId will not be undefined.
$scope.$watch(function () {
return EntityId
}, function (newEntityId) {
if(newEntityId != undefined {
// now you can call your function
}
}
}, true);
Exactly, you have to use promises, because $http module is asynchronus. I built a service for that:
.service('RequestService', function($q, $http){
return {
call: function(htmlOptions){
var d = $q.defer();
var promise = d.promise;
$http(htmlOptions)
.then(function(response){
d.resolve(response.data);
}, function(response){
d.reject(response.data);
});
promise.success = function(fn) {
promise.then(fn);
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
}
}
})
And then:
RequestService.call({
method: 'POST' //GET, DELETE ...
data: data,
url: 'http://someurl.com/'
});
you should use defer.
create a new defer object in GetGroupIdFromBakery method,in success part resolve the defer and in failed part reject it, and return the promise of defer at the end.
function GetGroupIdFromBakery(bakeryId, successCallback, errorCallback, $q) {
var defer = $q.defer();
$http.get(service.baseUrl + "BakeriesGroup/Bakeries/" + bakeryId)
.then(function (result) {
successCallback(result.data);
defer.resolve(result.data);
}, function(){
defer.reject();
});
return defer.promise;
}
This successfully return you a promise that you can call with .then() and receive as a value in the service where you need to have the data of GetGroupIdFromBakery.
getGroupOfBakery(parseInt(params.bakeryId), $q).then(function(data){
// here you have data
})
Be aware you should inject $q, here I supposed that we have $q in service and I passed it to method.

$http request wrapped in Service returns the whole response in resolve state Provider

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.

Nested $promise undefined error

I am getting the error Cannot read property of '$promise' of undefined.
Here is the code that is throwing it:
var myPromise = sharedDataService.getData($scope.entityId).$promise;
resolvePromise(myPromise, 'entityData');
the resolvePromise method:
function resolvePromise(promise, resultObject){
promise.then(function(response){
$scope[resultObject] = result;
});
promise['catch'](function(error){
//error callback
});
promise['finally'](function(){
//finally callback
});
sharedDataService looks like this:
var publicInterface = {
getData: getData
};
var storedData;
function getData(entityId) {
if(storedData.entityId === entityId){
return storedData;
}else{
var entityDataPromise = dataAccessService.getEntityData(entityId).$promise;
entityDataPromise.then(function (response) {
storedData = response;
return storedData ;
});
entityDataPromise['catch'](function(error) {
//throw error;
});
entityDataPromise['finally'](function(done) {
//do finally
});
}
}
return publicInterface;
finally, the dataAccessService:
var publicInterface = {
getEntityData: getEntityData
}
var entityData = $resource(apiUrl + 'Entity', {}, {
'getEntityData': {
method: 'GET',
url: apiUrl + 'Entity/getEntityDataById'
}
}
function getEntityData(entityId){
return entityData.getEntityData({entityId: entityId})
}
return publicInterface;
the original promise is throwing an error. When I put breakpoints various places, I can see my data is being returned sometimes. The functionality of sharedDataService is almost one of a chaching service.
Why is my original promise returning undefined?
Your getData() method doesn't have a return when if is false. So you would need to return entitiyDataPromise.
But, that would mean one condition returns a promise and the other returns an object
So both conditions need to return a promise and we can use $q for the first condition
function getData(entityId) {
if(storedData.entityId === entityId){
// return to getData()
return $q.resolve(storedData);
}else{
var entityDataPromise = dataAccessService.getEntityData(entityId).$promise;
// return to getData()
return entityDataPromise.then(function (response) {
storedData = response;
return storedData ;
});
entityDataPromise['catch'](function(error) {
//throw error;
});
entityDataPromise['finally'](function(done) {
//do finally
});
}
}
Be sure to inject $q in service.
In controller would be:
var myPromise = sharedDataService.getData($scope.entityId);
resolvePromise(myPromise, 'entityData');

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.

Angular $q catch block resolves promis?

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

Resources