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;
}
Related
I am porting an old ruby script over to use javascript setting the function as a cron instance so it will run on schedule. The function queries our mysql database and retrieves inventory information for our products and then sends requests to a trading partners api to update our inventory on their site.
Due to nodes a-synchronicity I am running into issues. We need to chunk requests into 1000 items per request, and we are sending 10k products. The issue is each request is just sending the last 1000 items each time. The for loop that is inside the while loop is moving forward before it finishes crafting the json request body. I tried creating anon setTimeout functions in the while loop to try and handle it, as well as creating an object with the request function and the variables to be passed and stuffing it into an array to iterate over once the while loop completes but I am getting the same result. Not sure whats the best way to handle it so that each requests gets the correct batch of items. I also need to wait 3 minutes between each request of 1000 items to not hit the request cap.
query.on('end',()=>{
connection.release();
writeArray = itemArray.slice(0),
alteredArray = [];
var csv = json2csv({data: writeArray,fields:fields}),
timestamp = new Date(Date.now());
timestamp = timestamp.getFullYear() + '-' +(timestamp.getMonth() + 1) + '-' + timestamp.getDate()+ ' '+timestamp.getHours() +':'+timestamp.getMinutes()+':'+timestamp.getSeconds();
let fpath = './public/assets/archives/opalEdiInventory-'+timestamp+'.csv';
while(itemArray.length > 0){
alteredArray = itemArray.splice(0,999);
for(let i = 0; i < alteredArray.length; i++){
jsonObjectArray.push({
sku: alteredArray[i]['sku'],
quantity: alteredArray[i]["quantity"],
overstockquantity: alteredArray[i]["osInv"],
warehouse: warehouse,
isdiscontinued: alteredArray[i]["disc"],
backorderdate: alteredArray[i]["etd"],
backorderavailability: alteredArray[i]["boq"]
});
}
var jsonObject = {
login: user,
password: password,
items: jsonObjectArray
};
postOptions.url = endpoint;
postOptions.body = JSON.stringify(jsonObject);
funcArray.push({func:function(postOptions){request(postOptions,(err,res,body)=>{if(err){console.error(err);throw err;}console.log(body);})},vars:postOptions});
jsonObjectArray.length = 0;
}
var mili = 180000;
for(let i = 0;i < funcArray.length; i++){
setTimeout(()=>{
var d = JSON.parse(funcArray[i]['vars'].body);
console.log(d);
console.log('request '+ i);
//funcArray[i]['func'](funcArray[i]['vars']);
}, mili * i);
}
});
});
You would need async/await or Promise to handle async actions in node js.
I am not sure if you have node version which supports Async/await so i have tried a promise based solution.
query.on('end', () => {
connection.release();
writeArray = itemArray.slice(0),
alteredArray = [];
var csv = json2csv({ data: writeArray, fields: fields }),
timestamp = new Date(Date.now());
timestamp = timestamp.getFullYear() + '-' + (timestamp.getMonth() + 1) + '-' + timestamp.getDate() + ' ' + timestamp.getHours() + ':' + timestamp.getMinutes() + ':' + timestamp.getSeconds();
let fpath = './public/assets/archives/opalEdiInventory-' + timestamp + '.csv';
var calls = chunk(itemArray, 1000)
.map(function(chunk) {
var renameditemsArray = chunk.map((item) => new renamedItem(item, warehouse));
var postOptions = {};
postOptions.url = endpoint;
postOptions.body = JSON.stringify({
login: user,
password: password,
items: renameditemsArray
});
return postOptions;
});
sequenceBatch(calls, makeRequest)
.then(function() {
console.log('done');
})
.catch(function(err) {
console.log('failed', err)
});
function sequenceBatch (calls, cb) {
var sequence = Promise.resolve();
var count = 1;
calls.forEach(function (callOptions) {
count++;
sequence = sequence.then(()=> {
return new Promise(function (resolve, reject){
setTimeout(function () {
try {
cb(callOptions);
resolve(`callsequence${count} done`);
}
catch(err) {
reject(`callsequence ${count} failed`);
}
}, 180000);
});
})
});
return sequence;
}
function makeRequest(postOptions) {
request(postOptions, (err, res, body) => {
if (err) {
console.error(err);
throw err;
}
console.log(body)
});
}
function chunk(arr, len) {
var chunks = [],
i = 0,
n = arr.length;
while (i < n) {
chunks.push(arr.slice(i, i += len));
}
return chunks;
}
function renamedItem(item, warehouse) {
this.sku = item['sku']
this.quantity = item["quantity"]
this.overstockquantity = item["osInv"]
this.warehouse = warehouse
this.isdiscontinued = item["disc"]
this.backorderdate = item["etd"]
this.backorderavailability= item["boq"]
}
});
Could you please try this snippet and let me know if it works?I couldn't test it since made it up on the fly. the core logic is in the sequenceBatch function. the The answer is based on an another question which explains how timeouts and promises works together.
Turns out this wasn't a closure or async issues at all, the request object I was building was using references to objects instead of shallow copies resulting in the data all being linked to the same object ref in the ending array.
The factory doesn't return anything, it just writes to a json file. I can't figure out how to use $q in this case to be sure it's done writing.
This is causing me problems in a controller, because even if I use a callback, the code does not execute in the correct order..
angular.module('jsonWrite', [])
.factory('JsonWrite', function($q) {
var nw = require('nw.gui');
var fs = require('fs');
var path = require('path');
var file = "myjsonfile.json";
var filePath = path.join(nw.App.dataPath, file);
var write = {};
write.writeJson = function(x){
var deferred = $q.defer();
fs.access(filePath, fs.F_OK, function(err) {
if (!err) {
fs.readFile(filePath, 'utf8', function(err, data) {
var myObj = JSON.parse(data);
myObj = x;
fs.writeFileSync(filePath, JSON.stringify(myObj));
});
}
else {
fs.open(filePath, "w", function(err, data) {
var myObj = {};
myObj = x;
fs.writeFileSync(filePath, JSON.stringify(myObj));
});
};
$q.resolve();
});
return deferred.promise;
};
return(write);
});
This is an example of the controller function, even if there's a callback, JsonWrite is not done writing before StateChanger.changeState executes. This causes heaps of trouble. If I put a $timeout on StateChanger, everything works fine - it executes after JsonWrite is done writing.
$scope.change = function(x){
function write(callback){
JsonWrite.writeJson(x);
callback();
};
function change(){
writemystuff(function(){
StateChanger.changeState(); // <- $timeout here and it works
})
};
change();
};
If anyone can give me an idea on what can be done, I'd be very grateful
Your factory looks fine. But you need to USE your promise - this means you need to use a ".then()" syntax:
write.writeJson().then(function() {StateChanger.changeState()});
Have a look at these examples:
https://docs.angularjs.org/api/ng/service/$q
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);
});
Just when I thought I had promises figured out, I'm stumped again. I am trying to use a recursive function to return a promise. It looks like it is working but the "then" portion never gets hit. I tried using $q.all but that is causing me a problem with multiple calls to my Web API. Rewriting the code to use recursion seemed like the answer, but I cannot get the "then" to execute. I figure that I must be missing something simple but I can't seem to figure out just what.
Here is the call to the function:
getClusterLink(linkcodes, returnString)
.then(function () {
value.vchTextBeforeQuestionCluster = $scope.returnString;
})
Here is the recursive function:
function getClusterLink(linkcodes, returnString) {
var deferred = $q.defer();
$scope.returnString = returnString;
if (linkcount < linkcodes.length) {
contractorService.gethyperlink(linkcodes[linkcount])
.success(function (data) {
var vchUrl = data[0].vchUrl;
var end = vchUrl.length;
var docID = vchUrl.substring(vchUrl.indexOf("=") + 1, end);
var vchLinkName = data[0].vchLinkName;
var yay = '' + vchLinkName + '';
var yCode = "|Y" + linkcodes[linkcount] + "~";
$scope.returnString = $scope.returnString.replaceAll(yCode, yay);
linkcount++;
return getClusterLink(linkcodes, $scope.returnString);
})
}
else {
deferred.resolve();
return deferred.promise;
}
};
The function itself works correctly. It hits the resolve and the return deferred.promise, but the "then" never fires.
Any assistance is greatly appreciated!
promise has to be returned by the function before resolve or rejecting it.
function getClusterLink(linkcodes, returnString) {
var deferred = $q.defer();
$scope.returnString = returnString;
if (linkcount < linkcodes.length) {
contractorService.gethyperlink(linkcodes[linkcount])
.success(function (data) {
var vchUrl = data[0].vchUrl;
var end = vchUrl.length;
var docID = vchUrl.substring(vchUrl.indexOf("=") + 1, end);
var vchLinkName = data[0].vchLinkName;
var yay = '' + vchLinkName + '';
var yCode = "|Y" + linkcodes[linkcount] + "~";
$scope.returnString = $scope.returnString.replaceAll(yCode, yay);
linkcount++;
})
return getClusterLink(linkcodes, $scope.returnString);
}
else {
deferred.resolve();
}
return deferred.promise;
};
.then is implemented on promise object. So as the function is returning the promise ,.then would work fine.
You can look at this sample https://jsbin.com/daseyu/edit?html,js,console,output
It works fine.
I think the problem is because you are returning getClusterLink in success. You can return in end of if loop and not in .success.
Hope this helps.
Your getClusterLink function does not return a promise in the case where contractorService.gethyperlink is called. I wonder you don't get an exception from that. And even if you always returned deferred.promise, it wouldn't be resolved in that branch.
But you should not use a deferred at all here. Just use $q.resolve, and chain onto the $http promise that gethyperlink returns. Notice that .success is deprecated, and does no chaining like then does - returning from that callback is pointless.
function getClusterLink(linkcodes, returnString) {
$scope.returnString = returnString;
if (linkcount < linkcodes.length) {
return contractorService.gethyperlink(linkcodes[linkcount])
// ^^^^^^
.then(function (data) {
// ^^^^^
var vchUrl = data[0].vchUrl;
var end = vchUrl.length;
var docID = vchUrl.substring(vchUrl.indexOf("=") + 1, end);
var vchLinkName = data[0].vchLinkName;
var yay = '' + vchLinkName + '';
var yCode = "|Y" + linkcodes[linkcount] + "~";
$scope.returnString = $scope.returnString.replaceAll(yCode, yay);
linkcount++;
return getClusterLink(linkcodes, $scope.returnString);
});
} else {
return $q.resolve();
}
}
I figured it out. Seems the problem was that I had var deferred = $q.defer() inside of the recursive function so it kept resetting the variable. Moving it outside of the function (like below) resolved the issue and the "then" now fires.
var thisdeferred = $q.defer();
function getClusterLink(linkcodes, returnString) {
if (linkcount < linkcodes.length) {
contractorService.gethyperlink(linkcodes[linkcount])
.success(function (data) {
var vchUrl = data[0].vchUrl;
var end = vchUrl.length;
var docID = vchUrl.substring(vchUrl.indexOf("=") + 1, end);
var vchLinkName = data[0].vchLinkName;
var yay = '' + vchLinkName + '';
var yCode = "|Y" + linkcodes[linkcount] + "~";
$scope.returnString = $scope.returnString.replaceAll(yCode, yay);
linkcount++;
return getClusterLink(linkcodes, $scope.returnString);
})
}
else {
thisdeferred.resolve();
}
return thisdeferred.promise;
};
Here is the line (50) where this is happening:
var meetingId = meeting._id.toString(),
And here is the full, relevant code:
var MongoClient = require('mongodb').MongoClient;
var assert = require('assert');
var ObjectId = require('mongodb').ObjectID;
var config = require('./config'),
xlsx = require('./xlsx'),
utils = require('./utils'),
_ = require('lodash'),
url = config.DB_URL;
var meetings = [];
function findNumberOfNotesByMeeting(db, meeting, callback) {
var meetingId = meeting._id.toString(),
meetingName = meeting.name.displayValue,
attendees = meeting.attendees;
host = meeting.host;
var count = 1, pending = 0, accepted = 0;
console.log("==== Meeting: " + meetingName + '====');
_.each(attendees, function(item) {
console.log(count++ + ': ' + item.email + ' (' + item.invitationStatus + ')');
if (item.invitationStatus == 'pending') { pending++; }
else if (item.invitationStatus == 'accepted') { accepted++; }
});
console.log("*** " + attendees.length + ", " + pending + "," + accepted);
db.collection('users').findOne({'_id': new ObjectId(host)}, function(err, doc) {
var emails = [];
if (doc.emails) {
doc.emails.forEach(function(e) {
emails.push(e.email + (e.primary ? '(P)' : ''));
});
}
var email = emails.join(', ');
if (utils.toSkipEmail(email)) {
callback();
} else {
db.collection('notes').find({ 'meetingId': meetingId }).count(function(err, count) {
if (count != 0) {
console.log(meetingName + ': ' + count + ',' + attendees.length + ' (' + email + ')');
meetings.push([ meetingName, count, email, attendees.length, pending, accepted ]);
}
callback();
});
}
});
}
function findMeetings(db, meeting, callback) {
var meetingId = meeting._id.toString(),
host = meeting.host;
db.collection('users').findOne({'_id': new ObjectId(host)}, function(err, doc) {
var emails = [];
if (!err && doc && doc.emails) {
doc.emails.forEach(function(e) {
emails.push(e.email + (e.primary ? '(P)' : ''));
});
}
var email = emails.join(', ');
if (utils.toSkipEmail(email)) {
callback();
} else {
db.collection('notes').find({ 'meetingId': meetingId }).count(function(err, count) {
if (count != 0) {
var cursor = db.collection('meetings').find({
'email': {'$regex': 'agu', '$options': 'i' }
});
}
callback();
});
}
cursor.count(function(err, count) {
console.log('count: ' + count);
var cnt = 0;
cursor.each(function(err, doc) {
assert.equal(err, null);
if (doc != null) {
findNumberOfNotesByMeeting(db, doc, function() {
cnt++;
if (cnt >= count) { callback(); }
});
}
});
});
});
}
MongoClient.connect(url, function(err, db) {
assert.equal(null, err);
findMeetings(db, function() {
var newMeetings = meetings.sort(function(m1, m2) { return m2[1] - m1[1]; });
newMeetings.splice(0, 0, [ 'Meeting Name', 'Number of Notes', 'Emails' ]);
xlsx.writeXLSX(newMeetings, config.xlsxFileNameMeetings);
db.close();
});
});
As you can see, the meeting variable (which I am almost 100% sure is the problem, not the _id property) is passed in just fine as a parameter to the earlier function findNumberOfNotesByMeeting. I have found some information here on SO about the fact that my new function may be asynchronous and needs a callback, but I've attempted to do this and am not sure how to get it to work, or even if this is the right fix for my code.
You're not passing the meeting object to findMeetings, which is expecting it as a second parameter. Instead of getting the meeting object, the function receives the callback function in its place, so trying to do meeting._id is undefined.
In fact, what is the purpose of the findMeetings function? It's name indicates it can either find all meetings in the database, or all meetings with a specific id. You're calling it without a meeting id indicating you might be trying to find all meetings, but its implementation takes a meeting object. You need to clear that up first.
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);
});