Using promise with closure in service with multiple $http.get - javascript

I've searched high and low but I just can't seem to wrap my head around q.defer() and creating my own promise.
I have a service getDataService which does exactly that - $http.gets data from a REST server. However only one of each variable can be sent at a time, so if user wants to query server for two entities and return the entire associated data they must send two requests. Because of this I had to use a method which kept i as the actual count (closure) which then runs my get data function the appropriate amount of times:
keepICorrect: function (security) {
var self = this;
for (var i = 0 ; i < entity.length; i++) {
self.getDataFromREST(security, i);
}
},
I call this from my main controller as a promise :
$scope.apply = function (security) {
var myDataPromise = getDataService.keepICorrect(security);
myDataPromise.then(function () {
//DO STUFF
}, 1);
}, function (error) {
alert("Error Retrieving Data");
return $q.reject(error);
});
}
This worked when using .getDataFromREST() but obviously doesn't now as I have to route through my new loop function, keepICorrect().
My question is how on earth do I create a promise which spans from my service to my controller, but not only that, also waits to resolve or fail depending on whether the i amount of requests have completed?

You need to create an array of promises
keepICorrect: function (security) {
var self = this;
var promises = [];
for (var i = 0 ; i < entity.length; i++) {
promises.push(self.getDataFromREST(security, i));
}
return promises;
},
And then wait for all of them to complete using the $q library in Angular
$q.all(getDataService.keepICorrect(security))
.then(....

Related

How to synchronously execute FOR LOOP using $q promises?

I have an array of Facebook groupIDs that I want to check in synchronous order, and if one fails, to skip to the next. When they all finish executing, I want to send all the results to my server.
The below code explains the gist of what I want to accomplish (of course it does not work):
var groups = [1111,2222,3333];
var feed_collection = [];
// Unfortunately this for loop does not wait for the FB api calls to finish before looping to next
for(var x = 0; x < groups.length; x++){
FB.api("/"+groups[x]+"/feed", function(response, err){
feed_collection += response;
});
}
// Send the feed_collection to server
feed_collection.sendToServer();
How can I get my for loop to wait? I know I can use $q.all(), however I am stuck on how to generate the promises and save them to a promise_array. This code is on the client-side and I am using AngularJS 1, but I am open to any approach. Mucho gracias!
function getGroupFeed(groupId) {
var deferred = $q.defer();
FB.api('/' + groupId + '/feed', function (response, err) {
if (err) return deferred.reject(err);
return deferred.resolve(response);
});
return deferred.promise;
}
That is how you can quickly generate a promise in Angular. Now you can also create an array of promises:
var groupPromises = groups.map(function (groupId) {
return getGroupFeed(groupId);
});
Or if you insist on a for loop:
var groupPromises = [];
for (var i = 0, len = groups.length; i < len; i++) {
groupPromises.push(getGroupFeed(group[i]));
}
This array of promises can then be used in $q.all, which is only resolved once all promises in the array are resolved.

Use results of one service call within another (Angular)

I'm currently making a call to a service method to return player profiles within my Angular app. I then need to immediately 'massage' some of the returned data by utilizing the results from another service method call. I'm wondering whether I am structuring my controller correctly below, or if I should make my codesService call within the $promise resolution of my playersService call instead.
Note: The controller below is a simplified version of my actual controller. The actual controller involves 'massaging' over a dozen elements.
'use strict';
angular.module('gameApp')
.controller('ProfileController', ProfileController);
ProfileController.$inject = ['$routeParams', 'playersService', 'codesService'];
function ProfileController($routeParams, playersService, codesService) {
var vm = this;
var playerId = $routeParams.playerId;
var codes;
var getCodes = function() {
codesService.get().$promise.then(function(data) {
codes = data;
});
};
var getProfiles = function() {
playersService.getProfiles({
playerId: playerId
}).$promise.then(function(profiles) {
for (var i = 0; i < profiles.length; i++) {
profiles[i].name = codes.MSP[profiles[i].MSPkey].name;
}
vm.profiles = profiles;
});
};
var init = function() {
getCodes();
getProfiles();
};
init();
}
Your controller should be very simple and get directly and exactly what it needs.
.controller("ProfileController", function(profileService, $routeParams){
var vm = this;
var playerId = $routeParams.playerId;
profileService.getProfiles(playerId)
.then(function(profiles){
vm.profiles = profiles;
});
});
profileService should just return everything neatly tied and packaged.
It will need to use the other services (i.e. codesService, playersService) as dependencies. And also, as I mentioned in the comments (and a few others in their answers), you can invoke the calls to codesService and playersService in parallel, but you must wait for both to finish.
.factory("profileService", function($q, codeService, playersService){
var svc = {
getProfiles: function(playerId){
$q.all({
players: getPlayers(playerId),
codes: getCodes()
})
.then(function(data){
var players = data.players;
var codes = data.codes;
return DoMassaging(players, codes);
})
}
};
return svc;
});
Make sure that getPlayers() and getCodes() return their respective promises.
If a then method returns a promise then Angular will use the results of the second promise for any subsequent then()] calls.
But all you really have here is a situation where you need both promises before you can progress, and for that you need is $q.all()
(Also, just return a promise from your service methods. The indirection doesn't improve your code.)
$q.all([getCodes(), getProfiles()).then(function(data)) {
var codes = data[0];
var profiles = data[1];
}
If you don't like the array arithmetic you can split it into two phases by chaining your then() calls.
What you have done looks dangerous, because if for any reason codeService.get() takes longer than playersService.getProfiles({..}) then you'll get an error beacuse codes will be undefined when you try to access codes.MSP[profiles[i].MSPkey].name.
$q.all(promises)
$q.all (docs page) takes an array or hash of promises and returns its own promise which will only be resolved when all of the others are:
$q.all({
codes: codesService.get().$promise,
profiles: playersService.getProfiles({
playerId: playerId
}).$promise
})
.then( function (results) {
var profiles = results.profiles;
var codes = results.codes;
for (var i = 0; i < profiles.length; i++) {
profiles[i].name = codes.MSP[profiles[i].MSPkey].name;
}
vm.profiles = profiles;
});

Structuring promises within angularjs

I have done a lot of reading around this, but ultimately the tutorials and guides I have found differ too much for me to get a decent grasp on this concept.
This is what I want to achieve:
1) Simple http request from our server [Any API for demonstration]
2) Run a function with data from (1). [Remove a property from the object]
3) Use result and length of (2) to run a loop of $http requests to our server. [Or any server]
4) This will result in 6 different objects. Run a function on these 6 objects. [Add a property]
5) Once ALL of this is done, run a separate function [Log "finished"]
How can this be achieved using promises? How do I pass data from (1) via a promise to (2)? Is this the right way to achieve what I need to do?
If anyone can show me how this should be structured it would be immensely helpful; I have kept the functions as simple as possible for this question.
Yes, promises are very nice to structure solutions for this kind of problems.
Simplified solution (more or less pseudo-code):
$http(...)
.then(function(response) {
// do something with response, for example:
var list = reponse.data.list;
// return it so that you can use it in the next 'then'.
return list;
})
.then(function(list) {
var promises = [];
angular.forEach(list, function(item) {
// perform a request for each item
var promise = $http(...).then(function(itemResponse) {
itemResponse.extraProperty = true;
return itemResponse;
});
// we make an array of promises
promises.push(promise);
});
// combine all promises into one and return it for the next then()
return $q.all(promises);
})
.then(function(itemsList) {
// itemsList is now an array of all parsed item responses.
console.log(itemsList);
});
(Hopefully this is right, I did not tested it.)
As you can see, you can return values in a callback to pass it to the next then(), or you can pass a promise, and this will result in calling the next callback when it resolves. $q.all() is used to combine multiple promises into one and resolve if all are resolved.
Edit: I realised that you can optionally leave out these three lines:
return list;
})
.then(function(list) {
But it is nice syntax though, because the separation of tasks is more visible.
Check code below, it could contains syntax error, the important is the structure. Step3 contains multiple(6) $http requests, it waits until the last request response to return a unique response object (array) containing response for each $http requets.
//Step 1
var Step1 = function () {
$http.get('api/controller').success(function (resp) {
var object1 = resp;
Step2(object1);
Step3(object1).then(function (resp) {
//resp.data is an array containing the response of each $http request
Step4(resp);
Step5();
});
});
}
//Step2
var Step2 = function(obj){
//do whatever with the object
}
//Step3
var Step3 = function (object1) {
var call = $q.defer();
var get1 = $http.get(object1[0].url);
var get2 = $http.get(object[1].url2);
//...
var get6 = $http.get(object[5].url6);
$q.all([get1, get2,..get6]).then(function (resp) {
call.resolve(resp);
});
return call.promise;
}
//Step4
var Step4 = function (resp) {
for (var i=0; i<resp.data.lenght;i++){
DoWhatEver(resp.data[i]);
};
}
//Step5
var Step5 = function () {
alert("Finished");
}
Step1(); //Call Step1 function
Don't know why you have difficulty implementing this, but maybe $q.all() is what you're missing:
var config1={method:'GET',url:'/api/...'};
$http(config1).success(function(resultsFrom1){
functionForResultsOf1(resultsFrom1);
})
var functionForResultsOf1 = function(resultsOf1){
//remove something from the result, assuming this is a synchronous operation
resultsOf1.splice()...;
var promises=makePromises(*pass whatever you want*);
$q.all(promises).then(function(aggregateOfAllCallsToServer){
angular.forEach(aggregateOfAllCallsToServer,function(data){
//do something to data from each call to the server
})
console.log("finished");
})
}
var makePromises = function(serverUrls){
var promises = [];
angular.forEach(serverUrls, function(url) {
var promise=$http({
url : '/api/'+url,
method: 'GET',
})
promises.push(promise);
});
return $q.all(promises);
}

AngularJS collecting data in a loop of $http calls

I have the following loop which I have to make on my client API. On each iteration of loop I must add data returned from the API call as an object to an object array, then at the end of the loop I need to display the content of the object array.
Due to the nature of JS code execution (asynchronous) displaying the object array content always return undefined, so I was wondering if someone can please help me with a solution to this problem. Thanks.
var invoiceObj = {};
var invoiceObjArray = [];
for (var i=0; i< 5; i++)
{
//getAllInvoices returns a promise from $http.GET calls...
ClientInvoiceService.getAllInvoices(i).then(function(invoice){
invoiceObj = { invoiceNum: invoice.Result[0].id,
clientName: invoice.Result[0].clientName};
invoiceObjArray.push(invoiceObj);
}, function(status){
console.log(status);
});
}
console.log(invoiceObjArray[0]); //return undefined
console.log(invoiceObjArray[1]); //return undefined
What you'll need to do is store all promises and then pass these to $q.all (scroll all the way down), which will wrap them in one big promise that will only be resolved if all are resolved.
Update your code to:
var invoiceObj = {};
var invoiceObjArray = [];
var promises = [];
for (var i=0; i< 5; i++)
{
//getAllInvoices returns a promise...
promises.push(ClientInvoiceService.getAllInvoices(i).then(function(invoice){
invoiceObj = { invoiceNum: invoice.Result[0].id,
clientName: invoice.Result[0].clientName};
invoiceObjArray.push(invoiceObj);
}, function(status){
console.log(status);
}));
}
$q.all(promises).then(function(){
console.log(invoiceObjArray[0]);
});
This Egghead video is a nice tutorial to using $q.all:

How to know when a long series of async calls is finished in AngularJS?

I have a service SQLService on my PhoneGap/AngularJS app that runs when the app is loading. It iterates through a long array of Guidelines, and makes a DB transaction for each one. How can I signal that the final transaction has been completed?
What I want to have happen is something like:
In the controller, call `SQLService.ParseJSON`
ParseJSON calls `generateIntersectionSnippets`
`generateIntersectionSnippets` makes multiple calls to `getKeywordInCategory``
When the final call to getKeywordInCategory is called, resolve the whole chain
SQLService.ParseJSON is complete, fire `.then`
I really don't understand how to combine the multiple asynchronous calls here. ParseJSON returns a promise which will be resolved when generateIntersectionSnippets() is completed, but generateIntersectionSnippets() makes multiple calls to getKeywordInCategory which also returns promises.
Here's a simplified version of what's not working (apologies for any misplaced brackets, this is very stripped down).
What I want to happen is for $scope.ready = 2 to run at the completion of all of the transactions. Right now, it runs as soon as the program has looped through generateIntersectionSnippets once.
in the controller:
SQLService.parseJSON().then(function(d) {
console.log("finished parsing JSON")
$scope.ready = 2;
});
Service:
.factory('SQLService', ['$q',
function ($q) {
function parseJSON() {
var deferred = $q.defer();
function generateIntersectionSnippets(guideline, index) {
var snippet_self, snippet_other;
for (var i = 0; i < guideline.intersections.length; i++) {
snippet_self = getKeywordInCategory(guideline.line_id, snippets.keyword).then(function() {
//Should something go here?
});
snippet_other = getKeywordInCategory(guideline.intersections[i].line_id, snippets.keyword).then(function() {
//Should something go here?
});
}
}
deferred.resolve(); //Is fired before the transactions above are complete
}
generateIntersectionSnippets();
return deferred.promise;
} //End ParseJSON
function getKeywordInCategory(keyword, category) {
var deferred = $q.defer();
var query = "SELECT category, id, chapter, header, snippet(guidelines, '<b>', '</b>', '...', '-1', '-24' ) AS snip FROM guidelines WHERE content MATCH '" + keyword + "' AND id='" + category + "';",
results = [];
db.transaction(function(transaction) {
transaction.executeSql(query, [],
function(transaction, result) {
if (result != null && result.rows != null) {
for (var i = 0; i < result.rows.length; i++) {
var row = result.rows.item(i);
results.push(row);
}
}
},defaultErrorHandler);
deferred.resolve(responses);
},defaultErrorHandler,defaultNullHandler);
return deferred.promise;
}
return {
parseJSON : parseJSON
};
}]);
I'd appreciate any guidance on what the correct model is to doing a chain of promises that includes an iteration across multiple async transactions- I know that how I have it right now is not correct at all.
You can use $q.all() to wait for a list of promises to be resolved.
function parseJSON() {
var deferred = $q.defer();
var promiseList = [];
for (var i = 0; i < guideline.intersections.length; i++) {
promiseList.push(getKeywordInCategory(guideline.line_id, snippets.keyword));
promiseList.push(getKeywordInCategory(guideline.intersections[i].line_id, snippets.keyword));
}
$q.all(promiseList).then(function() {
deferred.resolve();
});
return deferred.promise;
} //End ParseJSON

Categories

Resources