Angular - For Loop HTTP Callback/Promise - javascript

I am trying to write a loop which performs a number of http requests and adds each response to a list.
However, I don't think I am going about it quite the right way.
I think I am not implementing the required promises correctly. The console log after the for loop shows myList array as empty.
Code:
var _myList = []
function getStuff() {
var deferred = $q.defer()
var url = someUrl
$http.get(url).success(function(response) {
if ( response.array.length > 0 ) {
// loop starts here
for ( var i=0; i < response.array.length; i++ ) {
getThing(response.array[i].id);
};
// check the varibale here
console.log(_myList);
deferred.resolve('Finished');
} else {
deferred.resolve('No stuff exists');
};
}).error(function(error) {
deferred.reject(error);
});
return deferred.promise;
};
function getThing(thindId) {
var deferred = $q.defer()
var url = someUrl + thingId;
$http.get(url).success(function(response) {
_myList.push(response);
deferred.resolve(response);
}).error(function(error) {
deferred.reject(error);
});
return deferred.promise;
};

You can simplify your code as follows:
var allThings = response.array.map(function(id){
var singleThingPromise = getThing(id);
//return a single request promise
return singleThingPromise.then(function(){
//a getThing just ended inspect list
console.log(_myList);
})
});
$q.all(allThings).then(function(){
//only resolve when all things where resolved
deferred.resolve('Finished');
}, function(e){
deferred.reject('Something went wrong ' + e);
});

You indeed won't be able to populate _myList array with for-loop like you set up. Instead create an array of promises - one per data item in response.array and return it as inner promise.
function getStuff() {
var url = someUrl;
return $http.get(url).then(function(response) {
if (response.data.array.length > 0) {
return $q.all(response.data.array.map(function(data) {
return getThing(data.id);
}));
} else {
return 'No stuff exists';
}
});
}
function getThing(thindId) {
var url = someUrl + thingId;
return $http.get(url).then(function(response) {
return response.data;
});
}
After that you would use getStuff like this:
getStuff().then(function(myList) {
console.log(myList);
});

Related

Angular Service Not Filtering Data

I am fetching the details from an API using Angular Service and I want to return only the matching items. Meaning, after I type some text, I will click a button, which will call a function in a Controller which will call the Service to get the details.
Now my problem, is that it returns the entire list not the filtered list, I have stored filter result into an array I want to return that array(foundItems).
This is my code
(function() {
angular.module('NarrowItDownApp',[])
.constant('url',"https://davids-restaurant.herokuapp.com/menu_items.json")
.controller('NarrowItDownController',['$scope','MenuSearchService',function($scope,MenuSearchService) {
var items=this;
items.searchitem="";
items.getitems=function() {
MenuSearchService.getMatchedMenuItems(items.searchitem).then(function(response) {
this.found=response.data;
console.log(this.found);
})
.catch(function(response) {
this.found=response.data;
console.log(this.found);
});
};
}])
.service('MenuSearchService',['$http','url',function($http,url) {
var service=this;
service.getMatchedMenuItems=function(searchitem)
{
var foundItems=[];
var key;
return $http.get(url).success(function(data) {
for(var i=0;i<data.menu_items.length;i++)
{
var temp=data.menu_items[i];
//Old method
/*for(key in temp)
{
if(temp.hasOwnProperty(key))
{
console.log(temp[key])
}
}*/
Object.keys(temp).forEach(function(items)
{
if(searchitem==temp[items])
{
foundItems.push(temp);
}
});
};
console.log(foundItems);
return foundItems;
})
.error(function(data) {
console.log('error');
return data;
});
return foundItems;
};
}]);
})();
Now my problem, is that it returns the entire list not the filtered list, I have stored filter result into an array I want to return that array(foundItems).
The reason that the service returns the entire list is that the .success and .error methods ignore return values. Use .then and .catch instead.
service.getMatchedMenuItems=function(searchitem)
{
var foundItems=[];
var key;
//return $http.get(url).success(function(data) {
//USE .then method
return $http.get(url).then(function(response) {
var data = response.data;
for(var i=0;i<data.menu_items.length;i++)
{
var temp=data.menu_items[i];
Object.keys(temp).forEach(function(items)
{
if(searchitem==temp[items])
{
foundItems.push(temp);
}
});
};
console.log(foundItems);
return foundItems;
})
//.error(function(data) {
//USE .catch method
.catch(function(errorResponse) {
console.log(errorResponse.status);
//return data;
//THROW to chain rejection
throw errorResponse;
});
//return foundItems;
};
Also it is important to use a throw statement in the rejection handler. Otherwise the rejected promise will be converted to a successful promise.
For more information, see Angular execution order with $q.
You can use promise for that Promise
(function() {
angular.module('NarrowItDownApp', [])
.constant('url', "https://davids-restaurant.herokuapp.com/menu_items.json")
.controller('NarrowItDownController', ['$scope', 'MenuSearchService', function($scope, MenuSearchService) {
var items = this;
items.searchitem = "";
items.getitems = function() {
MenuSearchService.getMatchedMenuItems(items.searchitem).then(function(data) {
for (var i = 0; i < data.menu_items.length; i++) {
var temp = data.menu_items[i];
//Old method
/*for(key in temp)
{
if(temp.hasOwnProperty(key))
{
console.log(temp[key])
}
}*/
Object.keys(temp).forEach(function(items) {
if (searchitem == temp[items]) {
foundItems.push(temp);
}
});
};
this.found = foundItems);
console.log(this.found);
})
.catch(function(response) {
this.found = response.data;
console.log(this.found);
});
};
}])
.service('MenuSearchService', ['$http', 'url', function($http, url) {
var service = this;
service.getMatchedMenuItems = function(searchitem) {
var foundItems = [];
var key;
return $http.get(url);
};
}]);
})();

issue with pushing data into a new array while in a promise chain

I'm having trouble figuring out why my data is not being push into my new array, "results". newArr[0].mscd.g[i] is a list of several objects.
var axios = require('axios');
var moment = require('moment');
var _ = require('lodash');
var getData = function() {
return getNBASchedule().then(function(payload) {
return filterByMonth('January', payload);
}).then(function(result) {
return result
});
}
....
getData grabs the data from baseURL and returns a list of objects.
var getMonthlySchedule = function(data){
var results = [];
var newArr = data.slice(0, data.length);
for (var i = 0; i <= newArr[0].mscd.g.length; i++) {
if (newArr[0].mscd.g[i].v.tid === 1610612744 || newArr[0].mscd.g[i].h.tid === 1610612744) {
results.push(newArr[0].mscd.g[i]); <---- //does not seem to work
// however if I were to console.log(newArr[0].mscd.g[i],
// I would see the list of objects)
}
}
return results; <-- //when i console at this point here, it is blank
};
var getSchedule = function () {
return getData().then(function(pl) {
return getMonthlySchedule(pl)
})
};
var monthlyResults = function() {
return getSchedule().then(function(r) {
console.log("result", r)
return r
});
};
monthlyResults();
You don't know when getSchedule() is done unless you use a .then() handler on it.
getSchedule().then(function(data) {
// in here results are valid
});
// here results are not yet valid
You are probably trying to look at your higher scoped results BEFORE the async operation has finished. You HAVE to use .then() so you know when the operation is done and the data is valid.
Your code should simplify as follows :
var getData = function() {
return getNBASchedule().then(function(payload) {
return filterByMonth('January', payload);
});
}
var getMonthlySchedule = function(data) {
return data[0].mscd.g.filter(function(item) {
return item.v.tid === 1610612744 || item.h.tid === 1610612744;
});
};
var monthlyResults = function() {
return getData()
.then(getMonthlySchedule)
.then(function(r) {
console.log('result', r);
return r;
});
};
monthlyResults();
This may fix the problem. If not, then :
Check the filter test. Maybe those .tid properties are String, not Number?
Check that data[0].mscd.g is the right thing to filter.

Problems with an angular foreach waiting for http calls

I'm building an ionic project where users can play a tour (which the data is from an API)
Every tour has an amount of parts that users can play at a certain point on the map. This app must be able to be a 100% offline app, so when the user enters the code of a tour, the data must be retrieved from the API before the user can proceed (so the app will put all the data of the tour offline). Every part has an image, video, audio which is getting downloaded at start of the app.
The problem is that the function call, who is downloading all the data, is not synchronous. The console.log's are saying that the function already ends before all data is downloaded. Pieces of code below:
function getAndFillFullTour() {
vm.showLoader = true;
// load data
TourFactory.getFullTour(vm.tourData.number, function(data){
if(data.state == 'success'){
vm.tourData = data;
var test = downloadData(function(){
// hide loader and continue tour
});
} else {
console.log('error');
}
});
}
This function calls the factory who is getting the full tour including paths of images of each part which is needed to get downloaded on the users device. The downloadData function is the following function:
function downloadData(callback) {
angular.forEach(vm.tourData.parts, function(value, key){
var part = value;
var i = key;
if(part.image !== "") {
TourFactory.getPartImage(part, tourId, function(data){
vm.tourData.parts[i].partImage = data;
console.log('executed with picture ' + i);
});
}
});
if(callback)
callback();
}
Unfortunately, the forloop itself is performing synchronous but it is not waiting for the factory call to complete. I tried a lot of alternatives with promises, but without luck. Could anyone help? I need to wait for the http call to be finished in order to get a response from the downloadData call.
the getPartImage() is just an example, there are five functions like this each for loop which need to be completed first before I get a response in the downloadData call.
Take a look at $q.all or here- it is a promise helper function that can wait for multiple promises to complete. It's result is a promise as well, so you can chain it with other promises.
// Promise function that knows how to download a single part
function downloadPart(myurl) {
// return http promise
return $http({
method: 'GET',
url: myurl
});
};
// Aggregat epromise that downloads all parts
function downloadAllParts(parts) {
var defer = $q.defer(); // Setup return promise
var partsPromises = []; // Setup array for indivudual part promises
angular.forEach(parts, function(part) { // Iterate through each part
// Schedule download of a single
partsPromises.push(downloadPart(part));
});
// Wait for all parts to resolve
$q.all(partsPromises)
.then(function(data) {
// Returned data will be an array of results from each individual http promise
resData = [];
angular.forEach(data, function(partData) {
//handle each return part
resData.push(partData.data);
})
defer.resolve(resData); // Notify client we downloaded all parts
}, function error(response) { // Handle possible errors
console.log('Error while downloading parts'
response);
defer.reject('Error while downloading parts');
});
return defer.promise;
};
Then, in your client you can simply wait for the downloadAllParts to complete:
downloadAllParts(myParts)
.then(function(data) {
alert('Success!');
}, function(error) {
alert(error);
})
Since $q.all is a promise as well, you can get rid of defers all together:
// Aggregat epromise that downloads all parts
function downloadAllParts(parts) {
var partsPromises = []; // Setup array for indivudual part promises
angular.forEach(parts, function(part) { // Iterate through each part
// Schedule download of a single
partsPromises.push(downloadPart(part));
});
// Wait for all parts to resolve
return $q.all(partsPromises)
.then(function(data) {
// Returned data will be an array of results from each individual http promise
var resData = [];
angular.forEach(data, function(partData) {
//handle each return part
resData.push(partData.data);
})
return resData;
});
};
Here is a working jsfiddle: link
Thanks all! The following code worked for me. I merged the solutions from the comments with some own stuff, and this solution made it to work for me.
// Aggregat epromise that downloads all parts
function downloadAllParts(parts) {
vm.showLoader = true;
var defer = $q.defer(); // Setup return promise
var partsPromises = []; // Setup array for indivudual part promises
angular.forEach(parts, function(part, key) { // Iterate through each part
// Schedule download of a single
if(typeof part.image !== 'undefined' && part.image !== "") {
partsPromises.push(downloadPartImage(part));
}
if(typeof part.audio !== 'undefined' && part.audio !== "") {
partsPromises.push(downloadPartAudio(part));
}
if(typeof part.video !== 'undefined' && part.video !== "") {
partsPromises.push(downloadPartVideo(part));
}
if(key > 0){
vm.tourData.parts[key].available = false;
} else {
vm.tourData.parts[key].available = true;
}
});
// Wait for all parts to resolve
$q.all(partsPromises)
.then(function(data) {
// Returned data will be an array of results from each individual http promise
resData = [];
angular.forEach(data, function(partData) {
//handle each return part
resData.push(partData);
})
defer.resolve(resData); // Notify client we downloaded all parts
}, function error(response) { // Handle possible errors
console.log('Error while downloading parts' + response);
defer.reject('Error while downloading parts');
});
return defer.promise;
}
function downloadPartImage(part) {
var data = {
oid: tourId,
plid: part.image,
func: 'image'
};
return TourFactory.getSynchronousPartImage(part, tourId).then(function(data){
part.partImage = data.data;
return data;
});
};
function downloadPartAudio(part) {
var targetPath = cordova.file.externalDataDirectory + tourId + '/audio/' + part._id.$id + '.mp3';
var url = "https://www.tourtodo.com/gameinfo/" + part.audio;
var trustHosts = true;
var options = {};
return $cordovaFileTransfer.download(url, targetPath, {}, true).then(function (result) {
console.log('Save file on '+targetPath+' success!');
part.audioSrc = targetPath;
return result;
}, function (error) {
console.log('Error Download file');
console.log(JSON.stringify(error));
return error;
}, function (progress) {
console.log((progress.loaded / progress.total) * 100);
});
}
function downloadPartVideo(part) {
var targetPath = cordova.file.externalDataDirectory + tourId + '/video/' + part._id.$id + '.mp4';
var url = "https://www.tourtodo.com/gameinfo/" + part.video;
var trustHosts = true;
var options = {};
return $cordovaFileTransfer.download(url, targetPath, {}, true).then(function (result) {
console.log('Save file on '+targetPath+' success!');
part.videoSrc = targetPath;
return result;
}, function (error) {
console.log('Error Download file');
console.log(JSON.stringify(error));
return error;
}, function (progress) {
console.log((progress.loaded / progress.total) * 100);
});
}
function getAndFillFullTour() {
vm.showLoader = true;
// load data
TourFactory.getFullTour(vm.tourData.number, function(data){
if(data.state == 'success'){
vm.tourData = data;
downloadAllParts(vm.tourData.parts)
.then(function(data) {
vm.showLoader = false;
vm.showStartButton = true;
alertPopup = $ionicPopup.alert({
title: 'Gelukt!',
template: 'De tour is opgehaald. Druk op start om de tour te starten.'
});
localStorage.setItem('tourdata', JSON.stringify(vm.tourData));
console.log(JSON.parse(localStorage.getItem('tourdata')));
}, function(error) {
console.log('error');
console.log(error);
})
} else {
console.log('error');
}
});
}

Updating Json values with a promise

I want to populate some values in Json that are being calculated with angular-promises and these value should be updated after certain events.
I tried to call the factory which yields the values for example something like below and tried to call the functions GetWeeklyVal and GetDailyVal which are in charge of calculating the values :
this.salesList =
{"sales":[
{ "id":"A1", "dailyValue": GetDailyVal('A1'), "weeklyValue": GetWeeklyVal('A1')},
{ "id":"A2", "dailyValue": GetDailyVal('A2'), "weeklyValue": GetWeeklyVal('A2')}
]}
and in my controller I have:
$scope.sales= salesServices.salesList.sales;
but it didn't work. the values remain zero which is the default value in the application.
Why the values are not being updated and what would be a better solution?
update
This is the portion of the code I call the calculation functions: (I skip the portion to get the values based on passed id in here)
function GetDailyVal(id){
var dValue = 0;
salesService.getSales();
dValue = salesService.totalAmount;
return dValue;
}
this is the factory
.factory('salesService', ['$http', '$q'],
function salesInvoiceService($http, $q) {
var service = {
sales: [],
getSales: getSales,
totalAmount: 0
};
return service;
function getSales() {
var def = $q.defer();
var url = "http://fooAPI/salesinvoice/SalesInvoices"; //+ OrderDate filter
$http.get(url)
.success(function(data) {
service.sales = data.d.results;
setTotalAmount(service.sales);
def.resolve(service.sales);
})
.error(function(error){
def.reject("Failed to get sales");
})
.finally(function() {
return def.promise;
});
}
function setTotalAmount(sales){
var sum = 0;
sales.forEach(function (invoice){
sum += invoice.AmountDC;
});
service.totalAmount = sum;
}
})
I think there are some errors in your code.
I give some sample code here. I think this will help you.
This is a sample code in one of my application. Check it.
service.factory('Settings', ['$http','$q', function($http,$q) {
return {
AcademicYearDetails : function(Details) {
return $http.post('/api/academic-year-setting', Details)
.then(function(response) {
if (typeof response.data === 'object') {
return response.data;
} else {
return $q.reject(response.data);
}
}, function(response) {
return $q.reject(response.data);
});
},
newUser : function(details) {
return $http.post('/api/new-user', details);
}
}
}]);
The reason why its not working is:
dailyValue: GetDailyVal('A1')
Here, GetDailyVal makes an async ajax call to an api. For handling async requests, you have to return a promise as follows in your GetDailyVal function as follows:
function GetDailyVal() {
salesService.getSales().then(function(data) { //promise
dValue = salesService.totalAmount;
return dValue;
})
}
Same thing need to be done for weeklyValue.

Nodejs - SolrClient, how to wait for response

I got a question regarding the solr-client module of nodejs. I'm using this module for querying against a solr-index.
The module itself works fine as long as I don't have to wait for finishing of the query and as long I need the result only as a async result.
But currently I cannot find out, how I will be able to await the finishing of a search request and use the result in a sequential way.
I have the follwing method in my manager
SolrManager.prototype.promisedQuery = function(query, callback) {
var solrClient = solr.createClient(this.configuration.cores.page);
var docs = null;
var finished = false;
var deferred = Q.defer();
var request = solrClient.search(query, function(err,obj){
if (!err) {
if (obj.response.numFound > 0) {
deferred.resolve(obj.response.docs);
} else {
deferred.resolve(null);
}
} else {
deferred.reject(err);
}
});
var records = null;
var promise = deferred.promise;
promise.then(function(result) {
records = result;
}).fail(function(error){
records = error;
});
return records;
};
The problem here is, that I try to wait for the result of the query and use it as return value of "promisedQuery".
I try since days to use this method in a sequential call, also with different additional modules like "wait.for", "q", etc. but nothing seems to work.
The callback function of the solr-client will always be executed after the manager-method has already returned. Also the promise-methods will be even called after the return from the manager-method.
Can someone help me out on that topic or have some tips, how I can await the response of the solr-client-search operation and then give it back in a sequential way?
Thanks for any help.
Udo Gerhards
over one week, it seems now that I have found a solution:
SolrManager.prototype.promisedQuery = function(query, callback) {
var solrClient = solr.createClient(this.configuration.cores.page);
var docs = null;
var deferred = Q.defer();
var request = solrClient.search(query, function(err,obj){
if (!err) {
if (obj.response.numFound > 0) {
deferred.resolve(obj.response.docs);
} else {
deferred.resolve(null);
}
} else {
deferred.reject(err);
}
});
return deferred.promise;
};
in all other managers, which are calling the above function:
...
var dbPromise = this.solrManager.promisedQuery(query);
var _self = this;
return Q.async(function*(){
var result = yield dbPromise;
return result;
});
...
After first tests, it seems that synchronized methods will wait until the promise is settled.
The only thing is, that it runs only with NodeJs version 0.11.10, which supports generator functions, with activated --harmony-flag and "q"-module.
Best regards
Udo
You are just using the promises a bit incorrectly. Instead of returning records, you need to return 'deferred.promise'. It should look something like this (note that you don't need the callback you passed into promisedQuery).
SolrManager.prototype.promisedQuery = function(query) {
var solrClient = solr.createClient(this.configuration.cores.page),
deferred = Q.defer();
solrClient.search(query, function(err,obj){
if (!err) {
if (obj.response.numFound > 0) {
deferred.resolve(obj.response.docs);
} else {
deferred.resolve(null);
}
} else {
deferred.reject(err);
}
});
return deferred.promise;
};
To use it you would do something like:
SolrManager.promisedQuery(myquery)
.then(function (data) {
// data is whatever your 'resolved' in promisedQuery
}, function (err) {
// err is whatever you rejected in promisedQuery
});
based on rquinns answer I've changed the code like follows:
SolrManager.prototype.promisedQuery = function(query, callback) {
var solrClient = solr.createClient(this.configuration.cores.page);
var docs = null;
var finished = false;
var deferred = Q.defer();
var request = solrClient.search(query, function(err,obj){
if (!err) {
if (obj.response.numFound > 0) {
deferred.resolve(obj.response.docs);
} else {
deferred.resolve(null);
}
} else {
deferred.reject(err);
}
});
return deferred.promise;
};
...
DemoObject.prototype.toString = function() {
return SolrManager.promisedQuery(this.query).then(function(result){
return result['title'];
}).fail(function(error){
return error;
});
};
DemoObject.prototype.typeOf = function() {
return SolrManager.promisedQuery(this.query).then(function(result){
return result['title'];
}).fail(function(error){
return error;
});
};
I think, this is the right way to use the "promise"-object. But what happens when i do the follwing:
...
var demoObject = new DemoObject();
demoObject.query = "id:1";
console.log(''+demoObject);
...
or if I use "demoObject" by concatenating it to a string
...
var string = "Some string "+demoObject;
...
In case of the string concatenation, I'm currently not sure that the string will contain also the title field from the database. Same for console output.
Will nodejs be so intelligent that it resolves for e.g. the string concatenation "after" the results from the database will be available?
BR
Udo

Categories

Resources