How to check the state of an angular-promise? - angularjs

In my website, I have API's for twitter and facebook which enables the "mentions" feature (the one that pops up whenever we use the # symbol)
However, it is often that the access token for some feature is often expires resulting in the API not working. I store all my API's in an array and then I need to check if the token has failed or not resulting in resolved or rejected API promise.
This is an older code that needs changing due $q.all. As $q.all works whenever all the promises have been resolved, thus triggering the .then() call, this results in the .then() function NEVER working in my case (as the Facebook API is never working)
I need to find a condition where each API is checked and the .then() runs for only that API that is resolved (twitter in this case) and ignores the failed API (Facebook in this case)
if (selectedIds.allowed.TW) {
usersApi.push(TS.loginResource.getTwitterProfiles({
subUserId: selectedIds.allowed.TW,
name: searchTerm
}).$promise);
}
if (selectedIds.allowed.FB || selectedIds.allowed.FB_PAGE ||
selectedIds.allowed.FB_GROUP) {
$scope.post.showTags = true;
usersApi.push(TS.loginResource.getFbPages({
subUserId: selectedIds.allowed.FB_PAGE || selectedIds.allowed.FB
|| selectedIds.allowed.FB_GROUP,
name: searchTerm
}).$promise);
}
if (usersApi.length) {
$q.all(usersApi).then(function (responses) {
var tags1 = responses[0];
tags1.forEach(function (tag, i) {
tags1[i].name = tag.name.replace(/\"/g, "");
});
$scope.post.tags = tags1;
if (usersApi.length > 1) {
var tags2 = responses[1]
tags2.forEach(function (tag, i) {
tags2[i].name = tag.name.replace(/\"/g, "");
});
$scope.post.tags = $scope.post.tags.concat(tags2);
}
})
}
}, 500);
} else {
$scope.post.tags = [];
$scope.post.showTags = false;
}

I think you are looking to chain backup response that catch the api error and return a new success resolved promise on each specific api call before you wait on "all" of them.
apiCalls.push(doTwiterStuff().then(handleTwiterSuccess, handleApiFailure);
apiClass.push(doFBStuff().then(handleFbSuccess, handleApiFailure);
Promise.all(apiCalls).then(arr => {
arr.filter(x => !isNil(x)).forEach(x => doSomethingWithApiResult(x));
});
function handleApiFailure(x) {
...
return Promise.resolve(null);
}
Hopes this helps.

$q.all is not resilient1
If one of the promises is rejected, the $q.all is rejected with the first error.
To create a resilient composite promise, that is a promise that waits for all the promises to complete pass or fail, use .catch on each individual promise to convert the rejected promise to a successful promise.
var resilientPromises = [];
angular.forEach(promises, function(p) {
var resilientP = p.catch( function(result) {
//return to convert rejection to success
return result;
});
resilientPromises.push(resilientP);
});
$q.all(resilientPromises).then( function (results) {
//process results
});
The two things to take away from this answer:
A $q.all promise is not resilient. It is rejected with the first rejected promise.
A fulfilled promise can be created from a rejected promise by returning a value to the onRejected function of either the .then method or the .catch method.

Related

Data from ES6 promise doesn't render on the page until I click on it?

I an using Ionic for my app with a connection to Firebase to pull data. I created a promise in a factory to pull data down and thought it should render the data on the screen once it finishes but I'm getting nothing until I touch the screen?
I get no error and the data does indeed come.
Factory:
all: function () {
return new Promise ((resolve, reject) => {
firebase.database().ref('desks').once('value').then(snapshot => {
let desks = []
snapshot.forEach((child) => {
desks.push(child.val());
});
resolve(desks)
});
});
}
Calling the Factory:
Office.all().then(function (data) {
console.log(data);
$scope.officeList = data;
});
I feel I'm calling it wrong or else there is an Ionic method that I can't seem to find.
How will I be able to have the data render once it finishes the request?
Thanks in advance
ES6 promises are not integrated with the AngularJS framework. Use $q.when to convert ES6 promises to AngularJS promises. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc.
all: function () {
̶r̶e̶t̶u̶r̶n̶ ̶n̶e̶w̶ ̶P̶r̶o̶m̶i̶s̶e̶ ̶(̶(̶r̶e̶s̶o̶l̶v̶e̶,̶ ̶r̶e̶j̶e̶c̶t̶)̶ ̶=̶>̶ ̶{̶
var es6promise = firebase.database()
.ref('desks').once('value')
.then(snapshot => {
let desks = []
snapshot.forEach((child) => {
desks.push(child.val());
});
̶r̶e̶s̶o̶l̶v̶e̶(̶d̶e̶s̶k̶s̶)̶
return desks;
});
return $q.when(es6promise);
}
Also avoid using Promise(resolve,reject to create new promises. Instead use the promise returned by the .then method. It returns a new promise which is resolved or rejected via the return value of the successCallback, 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).
For more information, see You're Missing the Point of Promises.

Angular Promises and Chaining: How to break a chain if it has business data errors

I thought that I had this all figured out on previous projects through the years.. Apparently not.
Goal : Take Service that calls other Services and if there is any type of error being returned ( not a status of 200 ) then I need the async thing to be waiting and not proceeding.
Seems to me like I don't ever see really that great of examples as it is all very simplistic.
I read various articles about what Angular (1) is doing under the hood , and i see that there are $q, .then, .success etc..
Seems that I am having issues with return and with other nested and bundled service calls being made without any checking of a problem.
Essentially this image shows what is coming back
data : null ( that is bad)
errorList Array1 0 "This order cannot be submitted... " ( bad too)
hasErrors : true ( bad as well)
So that data is important to me to capture and display to user and then NOT move onto more processing
This is my order of operations
this.submitEnrollment = function (enrollment) {
return getSubmit(requestData);
}
// Which CALLS below
var getSubmit = function (request) {
return SparkRequestService
.submitRequest(request)
.then(
function (resData) {
console.log("resData", resData);
enrollmentService.resetEnrollment();
return resData;
},
function (resData) {
console.log('error');
}
);
}
Then I'm certainly calling SparkRequestService.submitRequest(request)
but based on the image attached, I am getting the error in the resData
So, it seems that I need to interrogate the resData right? So then I really should NOT ALLOW this other service to be called enrollmentService.resetEnrollment();
How can i refactor to stop from that getting processed? if statement in the .then ?
To prevent a rejection handler from converting a rejected promise to a fulfilled promise it is important use a throw statement in the rejection handler:
var getSubmit = function (request) {
return SparkRequestService
.submitRequest(request)
.then(
function (resData) {
console.log("resData", resData);
enrollmentService.resetEnrollment();
return resData;
},
function (errorResponse) {
console.log('error');
//IMPORTANT
//throw to chain rejection
throw errorResponse;
}
);
}
When a function omits a return or throw statement, the function returns a value of undefined. This will convert a rejected promise to a fulfilled promise that resolves with a value of undefined.
Problem is that ... it is a business error wrapped up in a return object
To convert a fulfilled promise to a rejected promise, use a throw statement.
this.submitEnrollment = function (enrollment) {
var promise = getSubmit(requestData);
var newPromise = promise.then(function(response) {
if (response.data.hasErrors) {
console.log(response.data.errorList);
response.data.errorList.push("submitEnrollent: Rejected");
//THROW to create rejection
throw response;
} else {
//RETURN response to chain success
return response;
}
});
return newPromise;
}
When a promise is converted to a rejection, all subsequent success handlers in the chain will be skipped. The chain will be followed until a rejection handler is found.
it seems that the promise returned by SparkRequestService.submitRequest(request) is not rejected when you get the error inside resData. For this reason the successCallback of then is invoked and not the second one, the errorCallback.
For this reason, inside the successCallback you need to inspect the data of resData to check errors and behave accordingly, for example:
var getSubmit = function (request) {
return SparkRequestService
.submitRequest(request)
.then(function (resData) {
console.log("resData", resData);
if(resData === null) { // Check for errors
// There is an error, manage it inside this block of code
// ...
// You can also create a rejected promise with $q.reject() and passing resData containing the errors
return $q.reject(resData);
} else {
// Call resetEnrollment() in the ELSE branch so, it is executed only if resData does not contain errors
enrollmentService.resetEnrollment();
return resData;
}
},
function (resData) {
console.log('error');
}
);
};

Is it okay to handle all the $http errors in controller?

In all my services, I'm just invoking REST services and returning the promises to the controllers. All the error's are handled at controllers using catch like below,
MyService.getData(url).then(getDataSuccess).catch(exception.catcher('Contact Admin : '));
My question here is, Since the real $http calls will be made at service, should I have to write catchers in service or catching in controller is fine?,
Scenario 1:
function getData(url){
return $http.get(url);
}
Scenario 2: (Nested calls to make combined results)
function getOtherData(url){
var defer = $q.defer();
$http.get(url).then(
function(response){
$http.get(nextService).then(
function(res){
defer.resolve('combined data');
}
)
}
);
return defer.promise;
}
Both the service method is not handling any errors. Instead it just returns the promise. Will there be any situation where this kind of exception handling will get failed?
Note: I have created decorators for handling javascript,angular errors and route errors separately. This question is particularly about $http service errors.
Yes what you have can fail triggering your catch because you have no reject().
You are using an anti-pattern creating your own promise and not chaining the nested request properly. Neither of those request rejections will be returned anywhere.
To be able to chain these get rid of the $q.defer() and do:
function getOtherData(url) {
// return beginning of promise chain
return $http.get(url).then(function (response) {
// return next promise
return $http.get(nextService).then(function (res) {
// combine and return the data
return {
d1 : response.data,
d2 : res.data
};
});
});
}
Now walk through the scenarios and each part of chain is intact.
Think of the chain as each then needs a return until the end of the chain
Scenario 2: (Nested calls to make combined results)
Failed Scenario
function getOtherData(url){
var defer = $q.defer();
$http.get(url).then(
function(response){
$http.get(nextService).then(
function(res){
defer.resolve('combined data');
}
)
}
);
return defer.promise;
}
This scenario will fail if the first $http.get has an error. The promise will hang and never get resolved. This is why we recommend avoiding using $q.defer to create promises from services that already return promises.
Instead return data and chain promises.
function getOtherData(url) {
var promise = $http.get(url);
var derivedPromise =
promise.then ( function (response) {
var data = response.data;
var nextPromise = $http.get(nextService);
var derivedNext = nextPromise.then(function(response) {
//return for chaining
return response.data;
});
//return composite for chaining
return $q.all([data, derivedNext]);
});
return derivedPromise;
};
The getOtherData(url) promise will be fulfilled with an array with the data from the two XHRs or it will be rejected with the first error response.
It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.1
Chaining error handlers
In an error handler, to convert a rejected resolution to a fulfilled resolution return data. To chain a rejection, throw the error.
For example:
promise = http.get(someUrl);
derivedPromise = promise.catch(function(errorResponse) {
if (fixable) {
fixedPromise = $http.get(fixedUrl);
//return to convert
return fixedPromise;
} else {
//throw to chain rejection
throw errorResponse;
}
};
By chaining error handlers, errors can be handled both by the service and the client of the service.
This makes it possible to implement powerful APIs like $http's response interceptors.1
Building on #georgeawg's answer, if you want to return multiple sets of data then you don't need nested calls.
function getOtherData(url) {
var promise1 = $http.get(url).then ( function (response) {
return response.data;
});
var promise2 = $http.get(nextService).then(function(response) {
return response.data;
});
return $q.all([promise1, promise2]);
};
Now the caller gets a promise that resolves to a list of the 2 data items (or is rejected if either request fails). The only real difference is that both requests are issues in parallel.
This generalises easily to a situation where you could have a list of urls, fetch them all in parallel and get an array of the response.data items.
Because you get back only a single promise that resolves to an array of data you can handle the result in the controller, but you only need one error handler.
MyService.getOtherData(url)
.then(getDataSuccess)
.catch(exception.catcher('Contact Admin : '));
Although the original question doesn't specify, it might be the case that the second url depends on the result from the first. You can handle that case here as well if you remember that you can call .then() multiple times on the same promise:
function getOtherData(url) {
var promise1 = $http.get(url).then ( function (response) {
return response.data;
});
var promise2 = promise1.then(function(response) {
// compute nextService from response.data here...
var nextService = foo(response.data);
return $http.get(nextService).then(function(response) {
return response.data;
});
});
return $q.all([promise1, promise2]);
};

Angular storing server data in a global service. How do I ensure it is there

I have a globalDataService in my app that reads a couple of entities from the server.
I only want to read the data once, and then serve it up via a method on the service. Here's a simplified version
angular.module("myApp").factory("globalData", ["siteResource", globalData]);
function globalData( siteResource) {
var sites = [];
siteResource.query().$promise.then(function(data){
sites = data;
},
function(response) {
//handle bad stuff
});
var getSites = function () { return sites; }
return { getSites: getSites };
}
and in my controller I just want to be able to do this
this.sites = globalData.getSites();
and know that the data is there, and if it isn't then something is wrong. What do I need to do in my service to make this happen, I've just wasted 2 hours trying to do something with $q but with no joy.
It's pot luck whether the globalData service has loaded the data or not when I need it, particularly when the app first loads....
Save the promise and return the promise. Create the promise once and reuse it.
angular.module("myApp").factory("globalData", ["siteResource", globalData]);
function globalData(siteResource) {
var promise;
function getSitesPromise () {
if (!promise) {
promise = siteResource.query().$promise;
promise = promise.catch( function (error) {
//handle bad stuff
});
};
return promise;
};
return { getSitesPromise: getSitesPromise };
}
What your are doing is pretty simple.
Prob making $http requests to your server to get data. But you don't want to make them every time just on init.
you can use $q like this ...
var promise1 = $http.get('/mypath1');
var promise2 = $http.get('/mypath2');
var promises = $q.all([promise1, promise2]); // resolves when all promises are resolved OR will reject when ONE promise rejects
promises.then(function(arrayContainingAllServerResponses) {
// do something
}).catch(function(error) {
// oops one of the requests failed
})
sorry but i dont have time for more detail - this might get you on the right track - cheers
this works because $http returns a promise :)
Remember if your have a promise then you can call THEN on it - the THEN code will be executed when the promise is resolved (not immediately). You can even chain THENs by returning a promise from a THEN. Hope this all helps.
If a method returns a promise then you can call THEN on its return value. REMEMBER $http returns a promise which is resolved or rejected when your server responds or the request times out!

Unit-testing, waiting on a promise when $http does NOT get invoked

I have a unittest where a service returns a promise. Sometimes that service might make a $http request, sometimes it won't (It implements a cache of sorts). In either case, it resolves the promise, but only in $httpBackend.flush() does it actually get around to making the callbacks. How can I cause the promises that are resolved to actually call the functions in the .then() like flush() does.
This one works just fine
resolved = jasmine.createSpy();
rejected = jasmine.createSpy();
employeeEventService.loadSchedules()
.then(resolved, rejected);
$httpBackend.flush(); // This causes the promise to resolve/reject
expect(resolved).toHaveBeenCalled();
expect(rejected).not.toHaveBeenCalled();
This one doesn't work because I can't call flush() (since the service never called $http)
resolved = jasmine.createSpy();
rejected = jasmine.createSpy();
employeeEventService.loadSchedules()
.then(resolved, rejected);
//$httpBackend.flush(); // Can't call this because this call is "cached"
expect(resolved).toHaveBeenCalled();
expect(rejected).not.toHaveBeenCalled();
Service code:
if(loaded.startOn <= params.startOn && loaded.endOn >= params.endOn
&& new Date() - lastFetch < 60000) {
deferred.resolve(loaded.schedules);
} else {
service.loading +=1;
config = {params: params, timeout:30*1000};
$http.get('/api/employee-schedules/', config)
.then(function(response) {
...
process the json response
...
deferred.resolve(loaded.schedules);
}, function(reason, status) {
$log.error("Failed to get schedules", reason, status);
deferred.reject(reason, status);
})
.finally(function() {
service.loading -=1;
});
}
return deferred.promise;
All promises, regardless of what pattern they are in, are resolved when you call $scope.$digest();
You're using a promise antipattern in your service, which is why you're encountering these issues. Instead, try this pattern:
function loadSchedules () {
return (cachedSchedules) ? $q.when(cachedSchedules) : asyncHTTPStuffs();
}
(Thanks to Benjamin Gruenbaum)
This way, you can return a thenable object, regardless of cache status, while not needing to call $httpBackend.flush() if you know the data is cached.

Resources