Structuring promises within angularjs - javascript

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

Related

Need to return data from $http query only after result list is processed

I have a function that calls another function which then does two things:
It does an http get to grab a list of IDs
It then loops through that list of IDs, makes another http get for each ID, and adds the result to the var 'dataList'.
I need to have it return the fully populated dataList as the result, then I can take that list and do something with it. I know I need to use promises for this but I'm having trouble getting the behavior I'm looking for. My latest attempt is below. This does return the expected list, but then it seems to get stuck in a loop - I think it's doing a return for every iteration of the map loop. Any suggestions would be appreciated.
getDataList(searchString, matchCase, rows, start).then(function(result) {
// Do something with result
});
getDataList: function(searchString, matchCase, rows, start) {
let body = {};
let dataList = [];
var url = getUrl();
var defer = $q.defer();
return $http.get(url).success(function(response) {
let dataIdList = [];
body = response.response.docs;
body.map(function getDataId(wfr) {
if (wfr.referenceType === 'data') {
dataIdList.push(wfr.referenceId);
}
});
dataList = dataIdList.map(function getData(dataId) {
dataSvc.getDataDetails(dataId).then(function(response) {
dataList.push(response);
});
});
defer.resolve(dataList);
}).error(function(result) {
defer.reject();
});
}
The .success and .error methods have been removed from the AngularJS framework.1 Avoid using the deferred anti-pattern.2 Use $q.all to resolve multiple AngularJS promises:
getDataList: function(searchString, matchCase, rows, start) {
var url = getUrl();
return $http.get(url).then(function(response) {
let body = response.data.response.docs;
let dataIdList =
body.filter(wfr => wfr.referenceType === 'data').map(_ => _.referenceId);
let dataListPromiseArr = dataIdList.map(dataId => {
return dataSvc.getDataDetails(dataId).then(function(response) {
return response.data;
});
});
return $q.all(dataListPromiseArr);
}).catch(function(response) {
console.log(response);
throw response;
});
}
For more information, see
AngularJS $q Service API Reference - $q.all
I think Promise.all() is what you are looking for.
You can push all the getDataDetails(dataId) requests as promises to an array, then do Promise.all(yourPromiseArray) and return what you want only after Promise.all has successfully finished.
I'd highly recommend looking into async/await during your research, as it could be quite helpful. Plenty of articles on the internet on how you can implement it, and many many StackOverflow questions to look through if you feel like you're stuck with it.
Hope this helps, good luck! :)

node.js: structure multiple API requests, work on them and combine them

currently I am struggeling a little bit with node.js (I am new to it) doing different API requests (Usabilla API), work on the results and then combine them in order to work on the whole set (e.g. export).
Requesting the API is not the problem but I can't get the results out to do some other stuff on it (asynchronous code drives me crazy).
Attached please find a overview how I thought to do this. Maybe I am totally wrong about this or maybe you have other more elegant suggestions.
My code works until I have to request the two different API "adresses" (they are provided) and then extract the results to do some other stuff.
My problem here is that there are nested functions with a promise and I cant figure out how to pass this through the parent function inside waterfall to get handled by the next function.
In the code, of course there is nothing parallel as shown in the diagram.
Thats another point, how to do that ? Simply nest parallel and series/ another waterfall inside waterfall ?
I am a little bit confused because that gets more and more complex for a simple problem when this would be done with synchronous code.
Here I build up all my request querys (at the moment 4):
function buildQuery(IDs,callback){
var i = 0;
var max = Object.keys(IDs).length;
async.whilst(
function(){return i < max},
function(callback){
FeedbackQuery[i] =
{
identifier: IDs[i].identifier,
query:
{id: IDs[i].id,
params: {since:sinceDate,}
}
};
i++;
callback(null,i);
})
console.log(FeedbackQuery);
callback (null,FeedbackQuery);
};
I then have to decide which type of query it is and add it to an object which should contain all the items of this identifier type:
function FeedbackRequest(FeedbackQuery,callback)
{
var i = 0;
var max = Object.keys(FeedbackQuery).length;
async.whilst(
function(){return i < max},
function (callback){
identifier = FeedbackQuery[i].identifier;
APIquery = FeedbackQuery[i].query;
switch(identifier)
{
case 'mobilePortal':
console.log(FeedbackQuery[i].identifier, 'aktiviert!');
var result = api.websites.buttons.feedback.get(APIquery);
result.then(function(feedback)
{
var item = Object.keys(feedbackResults).length;
feedbackResultsA[item] = feedback;
callback(null, feedbackResultsA);
})
break;
case 'apps':
console.log(FeedbackQuery[i].identifier, 'aktiviert!');
var result = api.apps.forms.feedback.get(APIquery);
result.then(function(feedback)
{
var item = Object.keys(feedbackResults).length;
feedbackResultsB[item] = feedback;
callback(null, feedbackResultsB);
})
break;
}
i++;
callback(null,i);
})
};
Currently the functions are bundled in an async waterfall:
async.waterfall([
async.apply(buildQuery,IDs2request),
FeedbackRequest,
// a function to do something on the whole feedbackResults array
],function (err, result) {
// result now equals 'done'
if (err) { console.log('Something is wrong!'); }
return console.log('Done!');
})
How it actually should be:
Structure
Thank you very much for any tips or hints!
I'm not proficient with async, and I believe if you'r new to this, it's harder than a simple Promise library like bluebird combine with lodash for helpers.
What I would do based on your schemas :
var firstStepRequests = [];
firstStepRequests.push(buildQuery());// construct your first steps queries, can be a loop, goal is to have firstStepRequests to be an array of promise.
Promise.all(firstStepRequests)
.then((allResults) => {
var type1 = _.filter(allResults, 'request_type_1');
var type2 = _.filter(allResults, 'request_type_2');
return {
type1: type1,
type2: type2
};
})
.then((result) => {
result.type1 = //do some work
result.type2 = //do some work
return result;
})
.then((result) => {
//export or merge or whatever.
});
Goal is to have a simple state machine.
UPDATE
If you want to keep identifier for a request, you can use props to have :
var props = {
id_1:Promise,
id_2:Promise,
id_3:Promise
};
Promise.props(props).then((results) => {
// results is {
id_1:result of promise,
id_2:result of promise,
etc...
}
})
You could do something like :
var type1Promises = getType1Requests(); //array of type 1
var type2Promises = getType2Requests(); // array of type 2
var props = {
type_1: Promise.all(type1Promises),
type_2: Promise.all(type2Promises)
}
Promise.props(props).then((result) => {
//result is : {
type_1: array of result of promises of type 1
type_2: array of result of promises of type 2
}
})

Handling multiple Ajax Requests inside another Ajax Request

I'm using angularjs' $http method to get multiple "parent" elements. In this Ajax Calls .success method, I have to iterate over the parent elements, and use yet another Ajax call for every parent element, to get its respective child elements. What I want in the end, is an Array containing all the child element objects, so I can display them using ng-repeat. That's why I want to collect all the child elements in an array first, and the write that array to the scope array I'm using to display, so angular will only update when all child elements are collected. I'm not that versed in using promises, but I think this should be possible by using them. The structure is basically:
.success(function(parentElements){
var tempChildElements = [];
$.each(parentElements, function(i, parentElement){
getChildElements(parentElement)
.success(function(childElements){
tempChildElements.concat(childElements);
})
})
});
$scope.childElements = tempChildElements;
});
Basically, I need to know when all the requests inside jQuery.each are finished.
EDIT:
So, I changed my code to incorporate your answers, and I think I'm close but it's still not working. What I got is:
$scope.loadChildren = function(){
var tempchildren = [];
var promises = [];
restApi.getOwnparents() //Returns $http.get promise
.then(function(parents){
parents.data.forEach(function(parent, i, parents){
promises.push(restApi.getOwnchildren(parent) //Returns $http.get promise
.then(function(children){
tempchildren = tempchildren.concat(children.data);
},function(msg){
console.log(msg);
}));
});
}, function(msg){
console.log(msg);
});
$q.all(promises).then(function(data){
//Never gets called
$scope.currentElements = tempchildren;
console.log(tempchildren);
});
};
EDIT 2:
I got it to work using suggestions from you guys, below is my code. Feel free to share improvements.
$scope.loadparents = function(){
var tempchildren = [];
var promises = [];
restApi.getOwnparents()
.then(function(parents){
parent.data.forEach(function(parent, i, parents){
promises.push(restApi.getOwnchildren(parent));
});
$q.all(promises).then(function(data){
console.log(data);
data.forEach(function(children){
tempchildren = tempchildren.concat(children.data);
});
$scope.currentElements = tempchildren;
});
});
};
Something like this might be a possibiliy. Loop through your parentElements calling getChildElements with that element. However the response from getChildElements will be a promise if your returning the $http call so push that into an array and pass that array to $q.all. When all your ajax calls resolve so will $q.all.
var parentElements = [10, 20, 30, 40],
promises = [];
parentElements.forEach(function(i){
//Each method is actually called here
promises.push(getChildElements(i));
});
//$q.all will resolve once all of your promises have been resolved.
$q.all(promises)
.then(function (data){
//handle success
console.log('All Good', data);
//Modify your response into what ever structure you need, map may be helpfull
$scope.childElements = data.map();
});
Most likely your ajax call won't be resolved by the time the array is passed to $q.all however another nice thing about promises is even if they are all resolved $q.all will resolve straight away instead.
See it in action. http://jsfiddle.net/ht9wphg8/
Each request itself returns a promise, which can then be put into an array and pass that array to $q.all().
The success() callback is deprecated and since you need to return promises you need to use then() anyway in your original request callback.
Here's a sample factory that would make all the requests and once done you would have the parent data of first request returned to controller:
app.factory('DataService', function($http, $q) {
return {
getData: function() {
// return initial promise
return $http.get('parent.json').then(function(resp) {
var parentArr = resp.data;
// create array of promises for child data
var promises = parentArr.map(function(parentItem, i) {
// return each child request promise to the array
return $http.get('child.json').then(function(resp) {
console.log('Child request #' + (i + 1) + ' completed');
// update parent item
parentItem.child = resp.data
});
});
// return the promise wrapping array of child promises
return $q.all(promises).then(function() {
console.log('All requests completed');
// when all done we want the parent array returned
return parentArr;
});
});
}
};
});
app.controller('MainCtrl', function($scope, DataService) {
DataService.getData().then(function(parentArr) {
console.log('Add data to scope')
$scope.parentArr = parentArr;
});
});
DEMO

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

angularjs - $http reading json and wait for callback

I am trying to read data from json and wait until data will be fetched into $scope.urls.content. So I write code:
$scope.urls = { content:null};
$http.get('mock/plane_urls.json').success(function(thisData) {
$scope.urls.content = thisData;
});
And now I am trying to write something like callback but that doesn't work. How can i do that? Or is there any function for this? I am running out of ideas ;/
Do you mean that ?
$http.get('mock/plane_urls.json').success(function(thisData) {
$scope.urls.content = thisData;
$scope.yourCallback();
});
$scope.yourCallback = function() {
// your code
};
You want to work with promises and $resource.
As $http itself returns a promise, all you got to do is to chain to its return. Simple as that:
var promise = $http.get('mock/plane_urls.json').then(function(thisData) {
$scope.urls.content = thisData;
return 'something';
});
// somewhere else in the code
promise.then(function(data) {
// receives the data returned from the http handler
console.log(data === "something");
});
I made a pretty simple fiddle here.
But if you need to constantly call this info, you should expose it through a service, so anyone can grab its result and process it. i.e.:
service('dataService', function($http) {
var requestPromise = $http.get('mock/plane_urls.json').then(function(d) {
return d.data;
});
this.getPlanesURL = function() {
return requestPromise;
};
});
// and anywhere in code where you need this info
dataService.getPlanesURL().then(function(planes) {
// do somehting with planes URL
$scope.urls.content = planes;
});
Just an important note. This service I mocked will cache and always return the same data. If what you need is to call this JSON many times, then you should go with $resource.

Categories

Resources