I have $scope.contacts with various customer and their addresses. I want to loop through all the addresses, send them to the google API and attach the lat/lng for later use in Google Maps.
Thats what I have so far:
//Get already the Geodata of the selected GroupContacts
for (var p = 0; p < $scope.contacts.length;p++) {
var street = $scope.contacts[p].address;
var zip = $scope.contacts[p].postcode;
var city = $scope.contacts[p].city;
vermittlungService.getGoogleData(street,zip,city).then(function(response){
$rootScope.contacts[p].lat = response.data.results[0].geometry.location.lat;
$rootScope.contacts[p].lng = response.data.results[0].geometry.location.lng; //attach to the $scope
});
console.log($scope.contacts);
};
What i understand that the [p] value in the async call changed already the value, so i can't attacht the correct lat/lng to the contact. I get error ": Cannot read property '1' of undefined" in console.
How can I wait with the loop untit the data has arrived and then continue.
Thanks!
edit: This is my get the Google Data service
//Fetch Google API from Adress - Returns geoDataObject
this.getGoogleData = function(street,zip,city) {
return $http.get('https://maps.googleapis.com/maps/api/geocode/json?address= ' + street + '+' + zip + '+' + city + APIKEY)
.then(function(response) {
return response;
})
};
SOLUTION: I need to wrap the logic in a function. Then it works!
for (var p = 0; p < $scope.contacts.length;p++) {
(function(p) {
setTimeout(function() {
var street = $scope.contacts[p].address;
var zip = $scope.contacts[p].postcode;
var city = $scope.contacts[p].city;
vermittlungService.getGoogleData(street,zip,city,apiKey).then(function(response){
$rootScope.contacts[p].lat = response.data.results[0].geometry.location.lat;
$rootScope.contacts[p].lng = response.data.results[0].geometry.location.lng; //attach to the $scope
}, function(error){
console.log(error);
});
}, 250*p);
})(p);
});
Have you tried wrapping process by anonymous function?
for (var p = 0; p < $scope.contacts.length;p++) {
!function() {
var street = $scope.contacts[p].address;
var zip = $scope.contacts[p].postcode;
var city = $scope.contacts[p].city;
vermittlungService.getGoogleData(street, zip, city).then(function(response) {
$rootScope.contacts[p].lat = response.data.results[0].geometry.location.lat;
$rootScope.contacts[p].lng = response.data.results[0].geometry.location.lng;
});
console.log($scope.contacts);
}();
};
Have you tried $q for resolving the promise in the service http call, then setting a timeout for the function? (I believe google maps api limits the number of calls to four per second, so you'd use 250 msec).
So your service call would look something like:
this.getGoogleData = function(street,zip,city,apiKey) {
var deferred = $q.defer();
$http.get('https://maps.googleapis.com/maps/api/geocode/json?address=' + street + '+' + zip + '+' + city +'&key=' + apiKey)
.then(function(response) {
deferred.resolve(response);
}, function(reason) {
deferred.reject(reason);
});
return deferred.promise;
};
And your series of calls as it iterates through your contacts list:
for (var p = 0; p < $scope.contacts.length;p++) {
(function(p) {
setTimeout(function() {
var street = $scope.contacts[p].address;
var zip = $scope.contacts[p].postcode;
var city = $scope.contacts[p].city;
vermittlungService.getGoogleData(street,zip,city,apiKey).then(function(response){
$rootScope.contacts[p].lat = response.data.results[0].geometry.location.lat;
$rootScope.contacts[p].lng = response.data.results[0].geometry.location.lng; //attach to the $scope
}, function(error){
console.log(error);
});
}, 250*p);
})(p);
});
Related
I'm trying to get the first 5 pages of search results with google custom search API ...
So far I've tried to achieve the result using nested function but with no luck.
I know that I'm messing with callback but, so far I've not figure out the correct way (without using promises library) to solve my problem.
Could some of you point me out in the right direction?
Thanks.
app.get('/assesment', function(req, res){
console.log('route: /assesment');
var api_key = '';
var customsearch = google.customsearch('v1');
var response = "";
var number_of_pages = 5;
var next_page = 1;
var exit = 0
const CX = 'XXXXX';
const API_KEY = 'XXXXX';
const SEARCH = 'Test Query';
console.log('start');
// console.log('QUERY PAGE: '+pages);
doSearch(CX, SEARCH, API_KEY, next_page, function(resp){
res.send(resp);
});
//
// Functions
//
function doSearch(_cx, _search, _api_key, _start, callback ){
var response = '';
customsearch.cse.list({ cx: _cx, q: _search, auth: _api_key, start: _start }, function (err, resp) {
if (err) {
response = JSON.stringify(err);
} else {
// Got the response from custom search
console.log('Result: ' + resp.searchInformation.formattedTotalResults);
if (resp.items && resp.items.length > 0) {
console.log('First result of '+resp.items.length+' is ' + resp.items[0].title);
for (var i = 0; i < resp.items.length; i++) {
response += resp.items[i].title+"<br>";
response += resp.items[i].link +"<br><hr>";
}
}
res = {
response: response,
next_page: resp.queries.nextPage
}
// res =
}
_start += 1;
if (_start < 6 ) {
doSearch(_cx, _search, _api_key, _start, _start*10+1,
function(resp){
response += resp;
});
}
if (callback && typeof callback === "function") callback(response);
});
};
});
You can use a third-party service like SerpApi to scrape Google and get back structured JSON.
Example using the Node.js library to get 4 page of results:
var gsr = require('GoogleSearchResults')
let serp = new gsr.GoogleSearchResults("demo")
serp.json({
q: "Coffee",
num: 10,
start: 30,
location: "Portland"
}, (result) => {
console.log(result)
})
I'm trying to retrieve data from my Firebase Database, and corresponding images from Firebase Storage. The problem is, my view does not want to update itself with the data.
If I try to simply fetch the data from my database, it works perfectly. Once I add functionality to fetch pictures (which takes slightly longer) it looks like my view simply looks immediately at the scope variable and does not wait for $scope.friendsinfo to update. I think I'm doing something wrong with my promises and should be using $q, but I have no idea how exactly. Can anyone tell me what the best way would be to go about this? Thanks a lot!
var friendsRef = firebase.database().ref('friendships/' + firebase.auth().currentUser.uid);
$scope.friends = $firebaseArray(friendsRef);
$scope.friendsinfo = [];
$scope.$watch('friends', function() {
var newfriends = $scope.friends;
var newfriendsinfo = [];
for(var i = 0; i < newfriends.length; i++){
var ref = firebase.database().ref('users/' + newfriends[i].$id);
var profilePicRef = firebase.storage().ref("profilepictures/" + newfriends[i].$id + "/profilepicture");
var picPromise = fetchPicture(profilePicRef);
var newfriendid = newfriends[i].$id;
var newfriendagreed = newfriends[i].agreed;
picPromise.then(function(data){
ref.once('value', function(snapshot){
newfriendsinfo.push({
id: newfriendid,
name: snapshot.val().name,
email: snapshot.val().email,
agreed: newfriendagreed,
profilepicture: data //This is the functionality that causes my view to not display the updated $scope.friendsinfo because it takes too long.
});
});
});
}
$scope.friendsinfo = newfriendsinfo;
alert($scope.friendsinfo.length);
}, true);
function fetchPicture(ref){
return ref.getDownloadURL().then(function(url) {
return url;
}).catch(function(error) {
alert("error");
});
}
I have not got your code properly but posting code which will you to guide that how to use promises with resolve approach :
function asyncGreet(name) {
var deferred = $q.defer();
setTimeout(function() {
deferred.notify('About to greet ' + name + '.');
if (okToGreet(name)) {
deferred.resolve('Hello, ' + name + '!');
} else {
deferred.reject('Greeting ' + name + ' is not allowed.');
}
}, 1000);
return deferred.promise;
}
var promise = asyncGreet('Robin Hood');
promise.then(function(greeting) {
alert('Success: ' + greeting);
}, function(reason) {
alert('Failed: ' + reason);
}, function(update) {
alert('Got notification: ' + update);
});
If anyone ever needs the solution, here it is. Turns out the problem is mainly caused by waiting for the for loop to finish, for which each item in term waits for another function to finish. This is how I was able to solve it. It's probably not optimal, but it'll do for now :)
var friendsRef = firebase.database().ref('friendships/' + firebase.auth().currentUser.uid);
$scope.friends = $firebaseArray(friendsRef);
$scope.friendsinfo = [];
$scope.$watch('friends', function() {
var newfriends = $scope.friends;
asyncUpdateFriendsInfo(newfriends).then(function(newlist){
$scope.friendsinfo = newlist;
});
}, true);
function fetchPicture(ref){
return ref.getDownloadURL().then(function(url) {
return url;
}).catch(function(error) {
alert("error");
});
}
function asyncUpdateFriendsInfo(newfriends){
var deferred = $q.defer();
var newfriendsinfo = [];
for(var i = 0; i < newfriends.length; i++){
var ref = firebase.database().ref('users/' + newfriends[i].$id);
var profilePicRef = firebase.storage().ref("profilepictures/" + newfriends[i].$id + "/profilepicture");
var picPromise = fetchPicture(profilePicRef);
var newfriendid = newfriends[i].$id;
var newfriendagreed = newfriends[i].agreed;
picPromise.then(function(data){
ref.once('value', function(snapshot){
newfriendsinfo.push({
id: newfriendid,
name: snapshot.val().name,
email: snapshot.val().email,
agreed: newfriendagreed,
profilepicture: data
});
}).then(function(){
if (newfriendsinfo.length == newfriends.length){
deferred.resolve(newfriendsinfo);
}
});
});
}
return deferred.promise;
}
I have a class "Team", which can have many Users.
I need to create a from/to relation every time a user is added to a Team. Every user can rate and be rated by all his teammates.
This is my Cloud code. I can't seem to get this working.
// add Team relations for every saved player
Parse.Cloud.afterSave("Team", function(request) {
// Team being saved
var team = request.object,
relation = team.relation("players"),
query = relation.query();
query.find({
// players on this Team
success: function(results) {
// all players on this Team
var i, j, player, teammate;
for (i = 0; i < results.length; i++) {
player = results[i];
// for each player add a new Rate relation (from/to)
for (j = 0; j < results.length; j++) {
teammate = results[i];
// if it's same player, escape relation
if (player == teammate) { continue; }
var Rate = Parse.Object.extend("Rate"),
rate = new Rate();
// create the relation from this player
var fromRelation = rate.relation('from');
fromRelation.add(player);
// to every other player on the team
var toRelation = rate.relation('to');
toRelation.add(teammate);
rate.save(null, {
success: function(_team) {
},
error: function(_team, error) {
}
});
}
}
response.success('rate relation success');
},
error: function(error) {
response.error('rate relation error');
}
});
});
Adding a player to a team is working great, it runs on Client side.
Classes setup on Data Browser:
Your class is "Rates" but you are using this code:
var Rate = Parse.Object.extend("Rate"),
rate = new Rate();
change to
var Rate = Parse.Object.extend("Rates"),
rate = new Rate();
Also, make sure your object is created before adding a relation.
var Rate = Parse.Object.extend("Rate"),
rate = new Rate();
[.... MOVED BELOW .....}
rate.save(null, {
success: function(_team) {
[.... now create and save relations ...]
},
Solved it this way.
The only thing annoying me is the fact that if I try this, I get an error:
"fromRelation is expecting _User, but got _Team", why?
player = results[i];
var fromRelation = rate.relation('from');
fromRelation.add(player);
I had to create 2 new vars, fromPlayer and toPlayer, although it seems dumb to me, anyway it works.
Parse.Cloud.define("updateTeamPlayers", function(request, response) {
var Team = Parse.Object.extend("Team"),
team = new Team();
team.id = request.params.id;
var relation = team.relation("players"),
query = relation.query();
query.find({
success: function(results) {
var i, j, player, teammate, queryArray = [], teammateRes = results;
for (i = 0; i < results.length; i++) {
player = results[i];
// for each player add a new Rate relation (from/to)
for (j = 0; j < teammateRes.length; j++) {
teammate = teammateRes[j];
// if it's same player, escape relation
if (player == teammate) { continue; }
var Rate = Parse.Object.extend("Rates"),
rate = new Rate();
// create the relation from this player
var fromPlayer = new Parse.User();
fromPlayer.id = player.id;
var fromRelation = rate.relation('from');
fromRelation.add(fromPlayer);
// to every other player on the team
var toPlayer = new Parse.User();
toPlayer.id = teammate.id;
var toRelation = rate.relation('to');
toRelation.add(toPlayer);
queryArray.push(rate);
}
}
Parse.Object.saveAll(queryArray, {
success: function(_allsaved) {
response.success('rate save success:' + _allsaved);
},
error: function(error) {
response.error('rate save error:' + error + ' =|= ' + error.message);
}
});
// end suucess
},
error: function() {
response.error("team failed");
}
});
});
For my project I have a server.js that calls a helper function place-search.js as shown below.
var express = require('express');
var server = express.Router();
var placeSearch = require("./helpers/place-search");
var obj = "hello";
server.use(function(req, res, next) {
console.log(req.method, req.url);
next();
});
server.post('/', function(req, res) {
/* get the object passed by the client's post request */
obj = req.body;
//console.log("Obj: " + obj);
/* send the confirmation back to the client */
res.status(200).send("body");
placeSearch.placeSearch(obj);
});
module.exports.server = server;
Here is my place-search.js :
var config = require("./config.js");
var Promise = require('bluebird');
var DistanceMatrix = require("./distance-matrix.js");
var GooglePlaces = Promise.promisifyAll(require("googleplaces"));
var googlePlaces = new GooglePlaces(config.apiKey, config.outputFormat);
var extract = require('./extract.js');
var combination = require('./combination_ver2.js');
var permutation = require('./permutation.js');
function placeSearch(obj) {
console.log("Inside place search!");
/**
* Place search - https://developers.google.com/places/documentation/#PlaceSearchRequests
*/
var arr = [];
var count = 0;
var rad = obj["radius"];
console.log("radius: " + rad);
var loc = obj["location"];
console.log("Location: " + loc);
var mode = obj["mode"];
var params = obj["params"];
/* client's keywords */
var arr;
var ar = [];
for (var i = 0; i < params; i++) {
arr[i] = obj[i];
console.log(arr[i]);
var param = {
location: loc,
radius: rad,
mode: mode,
keyword: arr[i]
};
ar.push(param);
}
console.log("before promises");
var promises = ar.map(function(name) {
return googlePlaces.placeSearch(name, function(response) {
arr.push(response);
console.log(response);
console.log(count++);
//If all responses have been returned
//Find combos and pass to distance-matrix
if (count == ar.length) {
var Matrix = new Array();
var result = new Array();
//to extract only lat and lng from arr.results
//Matrix = extract.extract(arr);
result = combination.combination(arr);
// NOW RESULT IS THE ARRAY OF ALL COMBINATION
// NOW RESULT IS THE ARRAY OF COMBINATIONS OF latlng pairs AND PASS IT TO FRONTEND
/*result.forEach(function(combo, index) {
console.log("combo" + combo)
DistanceMatrix.distanceMatrix(mode, combo, result.length);
});*/
// IF YOU WANT TO SEE PERMUTATION
//permutation.permutation(result);
console.log("combination results: " + result);
}
})
});
}
module.exports.placeSearch = placeSearch;
My problem is I do not know how to pass the result variable back to the server.js so that I can use that result as an input for another helper function. I can not for the life of me figure out how to do this. Any help at all would be greatly appreciated.
Well, I don't see your placeSearch function returning anything at all right now, nor doing any kind of callback. Your placeSearch function should expose a callback parameter, which then gets called once you have the answer you want to send back.
Your server file will then take action on that callback. Abbreviating your code, it'd look something like this:
server.post('/', function(req, res) {
/* get the object passed by the client's post request */
obj = req.body;
//console.log("Obj: " + obj);
placeSearch.placeSearch(obj, function(error, data){
/* send the data back to the client */
res.status(200).send(data);
});
});
To support that, your placeSearch function will have to call its callback when appropriate:
function placeSearch(obj, callback){
/* all the stuff you do to assemble your data */
// if (there_is_some_error):
if(err) return cb(err);
// when you have all your data available, no error has occurred
return cb(null, data);
}
Something else you might notice is that your ar.map won't work as you seem to expect. ar.map is a synchronous function, you're calling async code inside... not gonna work the way you think. It's a bit long for this post, but you should look at the async library from npm to manage an array of asynchronous requests to collect one combined result.
use callback your code looks like this:
function placeSearch(obj,callback) {
console.log("Inside place search!");
/**
* Place search - https://developers.google.com/places/documentation/#PlaceSearchRequests
*/
var arr = [];
var count = 0;
var rad = obj["radius"];
console.log("radius: " + rad);
var loc = obj["location"];
console.log("Location: " + loc);
var mode = obj["mode"];
var params = obj["params"];
/* client's keywords */
var arr;
var ar = [];
for (var i = 0; i < params; i++) {
arr[i] = obj[i];
console.log(arr[i]);
var param = {
location: loc,
radius: rad,
mode: mode,
keyword: arr[i]
};
ar.push(param);
}
console.log("before promises");
var promises = ar.map(function(name) {
return googlePlaces.placeSearch(name, function(response) {
arr.push(response);
console.log(response);
console.log(count++);
//If all responses have been returned
//Find combos and pass to distance-matrix
if (count == ar.length) {
var Matrix = new Array();
var result = new Array();
//to extract only lat and lng from arr.results
//Matrix = extract.extract(arr);
result = combination.combination(arr);
// NOW RESULT IS THE ARRAY OF ALL COMBINATION
// NOW RESULT IS THE ARRAY OF COMBINATIONS OF latlng pairs AND PASS IT TO FRONTEND
/*result.forEach(function(combo, index) {
console.log("combo" + combo)
DistanceMatrix.distanceMatrix(mode, combo, result.length);
});*/
// IF YOU WANT TO SEE PERMUTATION
//permutation.permutation(result);
console.log("combination results: " + result);
callback(null,result);
}
})
});
}
in server.js:
server.post('/', function(req, res) {
/* get the object passed by the client's post request */
obj = req.body;
//console.log("Obj: " + obj);
/* send the confirmation back to the client */
res.status(200).send("body");
placeSearch.placeSearch(obj,function(err,result){
if(!err){
console.log(result);
}
})
});
It seems like you're having trouble with the async operation. You'll want to return the promise from your place-search module. You'll also need to convert the callbacks from placeSearch into a promise.
EDIT: updated since googlePlaces.placeSearch doesn't return a promise
inside placeSearch
function placeSearch(obj) {
//...
var promises = ar.map(function(name) {
var placeDefer = Q.defer();
return googlePlaces.placeSearch(name, function(response) {
placeDefer.resolve(response); // or placeDefer.reject if a failure occurs
});
return placeDefer.promise;
});
return promises;
}
and in your route:
// I'm going to just assume Q promise library here
var Q = require("q");
server.post('/', function(req, res) {
/* get the object passed by the client's post request */
obj = req.body;
//console.log("Obj: " + obj);
/* send the confirmation back to the client */
res.status(200).send("body");
Q.all(placeSearch.placeSearch(obj))
.spread(function() {
var places = Array.prototype.slice.call(arguments);
// possibly call res.status(200).send("body"); here?
// only if you're trying to use the places in your response
});
});
I have two arrays:
$scope.grid.data and $scope.grid.backup
I use the following script to compare the data in each one element at a time:
for (var i = 0, len = $scope.grid.data.length; i < len; i++) {
if (!angular.equals($scope.grid.data[i], $scope.grid.backup[i])) {
var rowData = $scope.grid.data[i]
var idColumn = $scope.entityType.toLowerCase() + 'Id';
var entityId = rowData[idColumn];
entityService.putEntity($scope.entityType, entityId, $scope.grid.data[i])
.then(function (result) {
angular.copy(result, $scope.grid.data[i]);
angular.copy(result, $scope.grid.backup[i]);
}, function (result) {
alert("Error: " + result);
})
}
}
and the following to update the database:
putEntity: function (entityType, entityId, entity) {
var deferred = $q.defer();
EntityResource.putEntity({ entityType: entityType, entityId: entityId }, entity,
function (resp) {
deferred.resolve(resp);
}, function (resp) {
deferred.reject('Error updating');
});
return deferred.promise;
}
This script correctly notices the changes and updates the database.
However there is a problem when the putEntity returns with a result and it then tries to copy the result into $scope.grid.data[i] and
$scope.grid.backup[i]
This happens later and when it tries to do this it always tries to put it into element 11.
Does anyone have any ideas how I can ensure the returned data from putEntity is copied back into the correct element of the grid.data and grid.backup arrays?
You need to create a closure over i. What you can do is create a function
var updateGridData=function(entityType, entityId, gridDataToUpdate, gridIndex)
entityService.putEntity(entityType, entityId,gridDataToUpdate)
.then(function (result) {
angular.copy(result, $scope.grid.data[gridIndex]);
angular.copy(result, $scope.grid.backup[gridIndex]);
}, function (result) {
alert("Error: " + result);
})
}
So your main method becomes
for (var i = 0, len = $scope.grid.data.length; i < len; i++) {
if (!angular.equals($scope.grid.data[i], $scope.grid.backup[i])) {
var rowData = $scope.grid.data[i]
var idColumn = $scope.entityType.toLowerCase() + 'Id';
var entityId = rowData[idColumn];
updateGridData($scope.entityType, entityId, $scope.grid.data[i],i);
}
}
You can get some more idea from this question JavaScript closure inside loops – simple practical example