I am trying to make a Firebase database call inside of a loop and an outer firebase call. The inner Firebase database call is using data returned from the outer firebase call and the loop, which is why it is run within the outer one. The results then should be set into the state.
Problem
The value that is being retrieved in the inner Firebase database call is not being set in the state.
Theory
Since Firebase database calls are asynchronous, my guess is that the inner Firebase database call does not finish before the loop completes and sets the state.
Therefore, I created a promise for the inner Firebase database call, so that the loop would wait for the call to finish before moving onto the next item.
However, the value retrieved is still not being set.
Does anyone know why the loop does not wait for promise containing call to Firebase database?
MY ATTEMPT
userRef.on("value", function(snapshot) {
var snap = [];
// loop through each branch received from firebase
snapshot.forEach(function(data) {
var firstThingsFirst = data.val().firstThingsFirst;
var someID = data.val().someID;
var myPromise = new Promise(function(resolve, reject) {
userRef.child('somechild').child(someID).once('value').then(function(newSnapshot) {
console.log("newSnapshot = (below)");
console.log(newSnapshot.val());
resolve(newSnapshot.val());
}, function(error) {
// Something went wrong.
console.error("error (below)");
console.error(error);
reject("noValueFound")
});
});
var someValue = "";
myPromise.then(function(valueRetrieved) {
console.log(".then of promise is running...");
console.log("valueRetrieved = (below)");
console.log(valueRetrieved);
someValue = this.checkUndefined(valueRetrieved);
}.bind(this));
var array = {"firstThingsFirst": firstThingsFirst, "someValue": someValue};
snap.push(array);
});
this.setState({
snapshots: snap
});
}.bind(this));
Alternative Attempt:
userRef.on("value", function(snapshot) {
var snap = [];
// loop through each branch received from firebase
snapshot.forEach(function(data) {
var firstThingsFirst = data.val().firstThingsFirst;
var someID = data.val().someID;
var someValue = this.fetchValueByID(someID);
var array = {"firstThingsFirst": firstThingsFirst, "someValue": someValue};
snap.push(array);
});
this.setState({
snapshots: snap
});
}.bind(this));
fetchValueByID(someID) {
userProfileRef.child('someChild').child(someID).once('value').then(function(snapshot) {
console.log("snapshot (fetchValueByID) = (below)");
console.log(snapshot.val());
return snapshot.val();
})
}
I have also tried the approaches recommended by Firebase:
https://firebase.googleblog.com/2016/01/keeping-our-promises-and-callbacks_76.html
Thanks in advance.
Does anyone know why the loop does not wait for promise containing call to Firebase database?
The reason for this is that you require setState to be called after all the fetches are done. But, your code doesn't do anything to wait. You just carry on with the loop and once it's done, call setState. You never really know whether your fetches completed or not. You need a way to wait for all the the fetches. In a nutshell, there is a problem because of mixing of synchronous and asynchronous code.
You can try this. The idea is to map all the fetchValueByID (I've added a return in the beginning) calls into an array of promises and then wait for all of them to resolve (using Promise.all) before doing setState
userRef.on("value", function(snapshot) {
// loop through each branch received from firebase
// AND map to array of promises
var promises = [];
snapshot.forEach(function(data) {
var firstThingsFirst = data.val().firstThingsFirst;
var someID = data.val().someID;
promises.push(this.fetchValueByID(someID).then(function(someValue) {
return {
"firstThingsFirst": firstThingsFirst,
"someValue": someValue
};
}));
});
// Wait for all promises to resolve
Promise.all(promises).then(function(results) {
this.setState({
snapshots: results
});
}.bind(this))
}.bind(this));
fetchValueByID(someID) {
// Notice the return here
return userProfileRef.child('someChild').child(someID).once('value').then(function(snapshot) {
console.log("snapshot (fetchValueByID) = (below)");
console.log(snapshot.val());
return snapshot.val();
})
}
I've faked all the possible data and converted my solution to a simple-to-understand snippet below
var promises = [];
// Faking the snapshot
[{
a: 1,
b: 10
}, {
a: 2,
b: 20
}].forEach(function(data) {
var firstThingsFirst = data.a
var someID = data.b
promises.push(fetchValueByID(someID).then(function(someValue) {
return {
"firstThingsFirst": firstThingsFirst,
"someValue": someValue
};
}));
});
// Wait for all promises to resolve
Promise.all(promises).then(function(results) {
console.log(results);
});
function fetchValueByID(someID) {
// Dummy Promise resolution
// Notice the return here
return new Promise(function(resolve, reject) {
setTimeout(function() {
// Dummy manipulation
resolve(someID * 100);
});
}).then(function(snapshot) {
console.log("snapshot (fetchValueByID) = (below)");
console.log(snapshot);
return snapshot;
})
}
Related
function issuetype(agent) {
//let i = 0;
console.log('inside issuetype');
return admin.database().ref('Support/issuetype').once('value', function(snapshot) {
var data = snapshot.val();
var array = Object.values(data);
console.log('Issues are');
console.log(array);
agent.add(`Select your issue `); //works fine
for(const val of array){
agent.add(new Suggestion(`${val}`));
}
console.log(data);
});
}
function subtype(agent) {
let data;
let value;
let id;
console.log('inside subtype');
let harry = new Promise(function(resolve,reject){
admin.database().ref('Support/issuetype').once('value', function(snapshot) {
value = agent.parameters.sub;
console.log('inside promise');
data = snapshot.val();
console.log('Key'+Object.keys(data));
console.log('Value'+value);
id = Object.keys(data).find(key => data[key] === value);
console.log('Matched id');
console.log(id);
if(id){
resolve(id);strong text
}else{
reject('not resolved');
}
});
});
harry.then(function(res){
console.log('Type of id:'+typeof(res));
console.log('id is:'+res);
agent.add(`Select your sub issue ret`);
admin.database().ref('Support/issuesubtype/'+res).once('value', function(snap) {
var snapdata = snap.val();
var values = Object.values(snapdata);
console.log(typeof(values));
console.log('SubIssues are'); // displayed in console
console.log(values);
agent.add(`Select your sub issue `); // not displayed
return agent.add(`Select your sub issue `); // not displayed
for(const k of values){
agent.add(new Suggestion(`${k}`)); // not displayed
}
});
}).catch(function(rej){
console.log(rej);**strong text**
}).then(function(rej){
console.log('Irrespctive');
});
}
intentMap.set('issuetype', issuetype);
intentMap.set('subtype', subtype);
Function subtype is called by intentMap,
And inside it harry function returns a promise, once promise is resolved I am getting data from firebase and want to display it using agent.add
Getting expected output in console.log but agent.add is blank
Whereas agent.add is working in issuetype function
Part of the problem is that you're mixing Promises and callbacks, sometimes returning the Promise, and sometimes not. It is easiest if you keep a few guidelines in mind:
Make sure you return the Promise.
Make sure your call to agent.add() is inside the .then() clause.
If you're using callback functions, those can be switched to using Promises instead in most cases.
Keep in mind that you don't need to wrap this into a new Promise, since the Firebase calls you're making return a Promise if you don't give it a callback function.
For example, your line
admin.database().ref('Support/issuesubtype/'+res).once('value', function(snap) {
should probably better be rewritten as
return admin.database().ref('Support/issuesubtype/'+res).once('value')
.then( snap => {
The important points here are that you're returning the Promise and you're using a Promise instead of a callback to handle the function.
Array is initialized
var Collect = [];
So I have a promise arguments so that I'll have a asynchronous in execution since retrieving data from firebase which then push in the array Collect takes a bit of time. Here's my code:
function loadTables(){
var promise = getDataFirebase();
promise.then(function(){
console.log("firsst");
return ProcessOfData();
}).then(function(){
console.log(Collect); //when printed, it shows the elements collected from firebase so the array is not 0.
console.log(Collect.length); // but when printeed here. it gives me 0. why?
return EndProcessLog();
}).then(function(){
});
}
Codes when retrieving data from firebase:
function getDataFirebase(){
return new Promise (function(resolve,reject){
refReview.on("value", function(snap){
var data = snap.val();
for(var key in data){ //data retrieved must be REVIEWEE NAME, REWIEVER NAME, RATING, ,CONTENT
Collect.push({
"RevieweeName": data[key].revieweeID.firstname.concat(" ",data[key].revieweeID.lastname),
"ReviewerName": data[key].reviewerID.firstname.concat(" ",data[key].reviewerID.lastname),
rating:data[key].rating,
content: data[key].content
})
}//end of for loop
}); //end of snap
resolve();
});
}
Why does it not work? Because you are resolving the promise before the asynchronous method runs. The reason why the object shows the value is the console lazy loading the object.
What do you do? Move the resolve line after the for loop inside the callback.
refReview.on("value", function(snap) {
var data = snap.val();
for (var key in data) { //data retrieved must be REVIEWEE NAME, REWIEVER NAME, RATING, ,CONTENT
Collect.push({
"RevieweeName": data[key].revieweeID.firstname.concat(" ", data[key].revieweeID.lastname),
"ReviewerName": data[key].reviewerID.firstname.concat(" ", data[key].reviewerID.lastname),
rating: data[key].rating,
content: data[key].content
})
} //end of for loop
resolve(); < --RIGHT
}); //end of snap
// resolve(); <-- WRONG
Ideally with a promise you do not use global variables, you pass the value through the resolve.
var myPromise = new Promise(function(resolve, reject) {
var str = "Hello!";
resolve(str);
});
myPromise.then(function(value) {
console.log(value);
});
I have an AngularJS application, which I use promises to get data from firebase database.
Here is my home-controller:
$scope.wallets;
walletDAO.getWalletsByUserId(auth.uid)
.then(function(wallets){
$scope.wallets = wallets;
$scope.$apply();
})
.catch(function(error){
console.log(error);
});
These are my two methods inside an service I call walletDAO:
this.getWalletsByUserId = function(id) {
return new Promise(function(resolve, reject) {
//codigo aqui
var dbRef = database.ref("users/" + auth.currentUser.uid);
dbRef.on('value', function(data) {
//console.log("Wallet IDs Retrieved!");
var userOnDB = data.val();
var walletIds = userOnDB.wallets;
var wallets = [];
for (i = 0; i < walletIds.length; i++) {
var x = getWalletById(walletIds[i])
.then(function(wallet){
wallets.push(wallet);
})
.catch(function(error){
console.log(error);
});
}
resolve(wallets);
}, function(error) {
reject(error);
});
});
};
var getWalletById = function(id) {
return new Promise(function(resolve, reject) {
var dbRef = database.ref("wallets/" + id);
dbRef.on('value', function(data) {
//console.log("Wallet Retrieved!");
var wallet = data.val();
//console.log(wallet);
resolve(wallet);
}, function(error) {
reject(error);
});
});
};
The second method, getWalletById, receive an wallet ID and return an wallet object from the firebase database. this mehod is called on the first method, getWalletsByUserId inside a for loop, which should wait for the second method to return the wallet before iterate to the next, so it can push it into the array. The problem is that it dont wait and the code execute the .then() method on the home-controller before the resolving of the getWalletById, leaving the $scope.wallets empty.
Any advice?
Use $q.all() to wait for all sub-promises to complete
$q.all(walletIds.map(function(id){
return getWalletById(id);
})).then(function(wallets){
...
})
Instead of manufacturing an ES6 promise from the ref.on method, use the ref.once method and bring it into the AngularJS execution context with $q.when:
function getWalletById(id) {
var dbRef = database.ref("wallets/" + id);
var es6Promise = dbRef.once('value');
return $q.when(es6Promise);
}
Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc.
Use $q.all and promise chaining in the parent function:
this.getWalletsByUserId = function(id) {
var dbRef = database.ref("users/" + auth.currentUser.uid);
var es6Promise = dbRef.once('value')
.then(function(snapshot)
//console.log("Wallet IDs Retrieved!");
var userOnDB = snapshot.val();
var walletIds = userOnDB.wallets;
var promiseList = walletIds.map(function(id){
return getWalletById(id);
});
return $q.all(promiseList);
});
return $q.when(es6Promise);
};
The .then method returns a new promise which is resolved or rejected via the return value of the successCallback, errorCallback (unless that value is a promise, in which case it is resolved with the value which is resolved in that promise using promise chaining).
I've looked through several promise syntax, but I'm really not understanding it (including the ones that had examples). Most of them resolved a variable, but mine is a bit more complex, and I can't seem to get it running right.
I'm using Firebase to get the current user, and want it to run the next lines AFTER I get the user.
Here's my code:
componentDidMount() {
var promise = new Promise ((resolve, reject) => {
var user = fire.auth().currentUser};
resolve(
if(user) {
console.log('Favorites: requesting favorites');
fire.database().ref('/favourites/' + user.uid).once('value').then(function (snapshot) {
var recipes_obj = snapshot.val();
let recipes = [];
for (let id in recipes_obj) {
let recipe = recipes_obj[id];
recipe.id = id;
recipes.push(recipe);
console.log("recipes: ", recipes)
}
console.log("recipes outside", recipes);
this.setState({ recipes: recipes });
}.bind(this));
} else {
console.log('Favorites: no user')
}
)
}
I've also tried it like this
componentDidMount() {
var user = new Promise ((resolve, reject) => {
resolve(fire.auth().currentUser).then(() => {
if(user) {
console.log('Favorites: requesting favorites');
fire.database().ref('/favourites/' + user.uid).once('value').then(function (snapshot) {
var recipes_obj = snapshot.val();
let recipes = [];
for (let id in recipes_obj) {
let recipe = recipes_obj[id];
recipe.id = id;
recipes.push(recipe);
console.log("recipes: ", recipes)
}
console.log("recipes outside", recipes);
this.setState({ recipes: recipes });
}.bind(this));
} else {
console.log('Favorites: no user')
}
})
})
var user = fire.auth().currentUser
This code is not asynchronous. When this line executes, you have the user immediately in var user. You don't need to use a promise to do something in response to getting the user - you already have it.
The only time you need to create a new Promise object and resolve it is when you have some work that run asynchronously and you have no other promise to indicate when that work is complete.
In your code you also don't return your new promise to the caller. This means the promise is useless. Unless you hand a promise off to another bit of code that operates in response to its resolution, it doesn't really help the situation. In other words, your promise is not doing anything here.
The only bit of code that's asynchronous in your code is the fetch from the database:
fire.database().ref('/favourites/' + user.uid).once('value')
once() returns a promise. In your code, you're calling the then() method on that promise to do more work after the result is available.
Seems like your not actually calling your promise anywhere just declaring it inside your componentDidMount? Try something like this.. BTW, never used firebase so not sure on the below api use.
componentDidMount() {
var promise = new Promise ((resolve, reject) => {
let user = ()=> fire.auth().currentUser
resolve(user)
})
promise().then((user)=>{
if(user) {
return fire.database().ref(`/favourites/${user.uid}`).once('value')
}
})
.then((snapshot)=>{
//Do other stuff with snapshot
})
}
I understand using promises in simple scenarios but currently really confused on how to implement something when using a for loop and some updates to local sqlite database.
Code is as follows
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
var q = $q.defer();
for (var item in surveys) {
var survey = surveys[item];
// created as a closure so i can pass in the current item due to async process
(function(survey) {
ajaxserviceAPI.postSurvey(survey).then(function(response) {
//from response update local database
surveyDataLayer.setLocalSurveyServerId(survey, response.result).then(function() {
q.resolve; // resolve promise - tried only doing this when last record also
})
});
})(survey) //pass in current survey used to pass in item into closure
}
return q.promise;
}).then(function() {
alert('Done'); // This never gets run
});
Any help or assistance would be appreciated. I'm probably struggling on how best to do async calls within loop which does another async call to update and then continue once completed.
at least promises have got me out of callback hell.
Cheers
This answer will get you laid at JS conferences (no guarantees though)
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
return Promise.all(Object.keys(surveys).map(function(key) {
var survey = surveys[key];
return ajaxserviceAPI.postSurvey(survey).then(function(response){
return surveyDataLayer.setLocalSurveyServerId(survey, response.result);
});
}));
}).then(function() {
alert('Done');
});
This should work (explanations in comments):
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
// array to store promises
var promises = [];
for (var item in surveys) {
var survey = surveys[item];
// created as a closure so i can pass in the current item due to async process
(function(survey) {
var promise = ajaxserviceAPI.postSurvey(survey).then(function(response){
//returning this promise (I hope it's a promise) will replace the promise created by *then*
return surveyDataLayer.setLocalSurveyServerId(survey, response.result);
});
promises.push(promise);
})(survey); //pass in current survey used to pass in item into closure
}
// wait for all promises to resolve. If one fails nothing resolves.
return $q.all(promises);
}).then(function() {
alert('Done');
});
Awesome tutorial: http://ponyfoo.com/articles/es6-promises-in-depth
You basically want to wait for all of them to finish before resolving getSurveysToUpload, yes? In that case, you can return $q.all() in your getSurveysToUpload().then()
For example (not guaranteed working code, but you should get an idea):
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
var promises = [];
// This type of loop will not work in older IEs, if that's of any consideration to you
for (var item in surveys) {
var survey = surveys[item];
promises.push(ajaxserviceAPI.postSurvey(survey));
}
var allPromise = $q.all(promises)
.then(function(responses) {
// Again, we want to wait for the completion of all setLocalSurveyServerId calls
var promises = [];
for (var index = 0; index < responses.length; index++) {
var response = responses[index];
promises.push(surveyDataLayer.setLocalSurveyServerId(survey, response.result));
}
return $q.all(promises);
});
return allPromise;
}).then(function() {
alert('Done'); // This never gets run
});