I am trying to push data to Firebase Real-time Database and after the data is pushed(and saved), the browser should open another page. I have used "location.replace()" function to open the next page however adding the location.replace line makes the data not to be saved in Firebase real-time database.
Here is my code
var updates = {};
updates['/users/' + document.getElementById('username').value] = data;
firebase.database().ref().update(updates);
console.log("Saved successfully")
location.replace("nextpage.html");
The update function is asyncronous; it will take some time to complete. If you want to wait until the update is done, then you will need to use the promise it returns:
var updates = {};
updates['/users/' + document.getElementById('username').value] = data;
firebase.database().ref().update(updates)
.then(() => {
console.log('Saved successfully');
location.replace('nextpage.html');
});
Or with async/await:
async function someFunction () {
var updates = {};
updates['/users/' + document.getElementById('username').value] = data;
await firebase.database().ref().update(updates);
console.log("Saved successfully")
location.replace("nextpage.html");
}
the update seems to be asynchronous function, returning a promise.
so you should properly handle it. Otherwise call to "location" may come before the update is finished.
Change it like this:
firebase.database().ref().update(updates).then(() => {
console.log("Saved successfully")
location.replace("nextpage.html");
}).catch(error => {console.log('Error:', error)})
If you don't want to use promise, provide an additional argument to the update function that will serve as a callback function, i.e will be called when update is finished:
firebase.database().ref().update(updates, function() {
console.log("Saved successfully")
location.replace("nextpage.html");
})
Related
I'm using Firebase along with angularJS to fetch a list of users. I can read all the users from my database with the once() function, but I can't figure out why userList returns undefined below
.service('userService', [function() {
this.getUsers = function() {
var users;
var userList;
var ref = firebase.database().ref('/users/');
ref.once('value').then(function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
}).then(function(){
userList = users;
console.log(userList); // outputs all users
},(function(error){
alert('error: ' + error);
});
console.log(userList); // outputs 'undefined'
}
}]);
I wait to assign my userList variable until I'm done processing users, but no luck.
There is something important here that I am missing where Promises/callbacks are concerned and I cannot find it in the documentation; could someone help me understand my issue?
The problem is (as imjared says) that the data is read from Firebase asynchronously. So the code doesn't execute in the order that you think. It's easiest to see that by simplifying it with just a few log statements:
.service('userService', [function() {
this.getUsers = function() {
var ref = firebase.database().ref('/users/');
console.log("before attaching listener");
ref.once('value').then(function(snapshot) {
console.log("got value");
});
console.log("after attaching listener");
}
}]);
The output of this will be:
before attaching listener
after attaching listener
got value
Knowing the order in which this executes should explain perfectly why you cannot print the user list after you've just attached the listener. If not, I recommend also reading this great answer: How to return the response from an asynchronous call
Now for the solution: you will either need to use the user list in the callback or return a promise.
Use the user list in the callback
This is the oldest way to deal with asynchronous code: move all code that needs the user list into the callback.
ref.once('value', function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
}
console.log(users); // outputs all users
})
You're reframing your code from "first load the user list, then print its contents" to "whenever the user list is loaded, print its contents". The difference in definition is minor, but suddenly you're perfectly equipped to deal with asynchronous loading.
You can also do the same with a promise, like you do in your code:
ref.once('value').then(function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
});
But using a promise has one huge advantage over using the callback directly: you can return a promise.
Return a promise
Often you won't want to put all the code that needs users into the getUsers() function. In that case you can either pass a callback into getUsers() (which I won't show here, but it's very similar to the callback you can pass into once()) or you can return a promise from getUsers():
this.getUsers = function() {
var ref = firebase.database().ref('/users/');
return ref.once('value').then(function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
return(users);
}).catch(function(error){
alert('error: ' + error);
});
}
With this service, we can now call getUsers() and use the resulting promise to get at the users once they're loaded:
userService.getUsers().then(function(userList) {
console.log(userList);
})
And with that you have tamed the asynchronous beast. Well.... for now at least. This will keep confusing even seasoned JavaScript developers once in a while, so don't worry if it takes some time to get used to.
Use async and await
Now that the function returns a promise, you can use async/await to make the final call from above look a bit more familiar:
function getAndLogUsers() async {
const userList = await userService.getUsers();
console.log(userList);
}
You can see that this code looks almost like a synchronous call, thanks to the use of the await keyword. But to be able to use that, we have to mark the getAndLogUsers (or whatever the parent scope of where we use await) as async, which means that it returns a Future too. So anyone calling getAndLogUsers will still need to be aware of its asynchronous nature.
You need to think async:
.service('userService', [function() {
this.getUsers = function() {
var users;
var ref = firebase.database().ref('/users/');
<!-- this happens ASYNC -->
ref.once('value', function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
},function(error){
alert('error: ' + error);
});
<!-- end async action -->
console.log(users); // outputs 'undefined'
}
}]);
I bet if you did this, you'd be fine:
.service('userService', [function() {
this.getUsers = function() {
var users;
var ref = firebase.database().ref('/users/');
<!-- this happens ASYNC -->
ref.once('value', function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
},function(error){
alert('error: ' + error);
});
<!-- end async action -->
window.setTimeout( function() {
console.log(users); // outputs 'undefined'
}, 1500 );
}
}]);
Clarification per Franks's comment:
I should clarify further that the setTimeout would just prove that this is an issue of timing. If you use setTimeout in your app, you're probably going to have a bad time as you can't reliably wait n milliseconds for results, you need to get them then fire a callback or resolve a promise after you have gotten the snapshot of the data.
I'm using Firebase along with angularJS to fetch a list of users. I can read all the users from my database with the once() function, but I can't figure out why userList returns undefined below
.service('userService', [function() {
this.getUsers = function() {
var users;
var userList;
var ref = firebase.database().ref('/users/');
ref.once('value').then(function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
}).then(function(){
userList = users;
console.log(userList); // outputs all users
},(function(error){
alert('error: ' + error);
});
console.log(userList); // outputs 'undefined'
}
}]);
I wait to assign my userList variable until I'm done processing users, but no luck.
There is something important here that I am missing where Promises/callbacks are concerned and I cannot find it in the documentation; could someone help me understand my issue?
The problem is (as imjared says) that the data is read from Firebase asynchronously. So the code doesn't execute in the order that you think. It's easiest to see that by simplifying it with just a few log statements:
.service('userService', [function() {
this.getUsers = function() {
var ref = firebase.database().ref('/users/');
console.log("before attaching listener");
ref.once('value').then(function(snapshot) {
console.log("got value");
});
console.log("after attaching listener");
}
}]);
The output of this will be:
before attaching listener
after attaching listener
got value
Knowing the order in which this executes should explain perfectly why you cannot print the user list after you've just attached the listener. If not, I recommend also reading this great answer: How to return the response from an asynchronous call
Now for the solution: you will either need to use the user list in the callback or return a promise.
Use the user list in the callback
This is the oldest way to deal with asynchronous code: move all code that needs the user list into the callback.
ref.once('value', function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
}
console.log(users); // outputs all users
})
You're reframing your code from "first load the user list, then print its contents" to "whenever the user list is loaded, print its contents". The difference in definition is minor, but suddenly you're perfectly equipped to deal with asynchronous loading.
You can also do the same with a promise, like you do in your code:
ref.once('value').then(function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
});
But using a promise has one huge advantage over using the callback directly: you can return a promise.
Return a promise
Often you won't want to put all the code that needs users into the getUsers() function. In that case you can either pass a callback into getUsers() (which I won't show here, but it's very similar to the callback you can pass into once()) or you can return a promise from getUsers():
this.getUsers = function() {
var ref = firebase.database().ref('/users/');
return ref.once('value').then(function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
return(users);
}).catch(function(error){
alert('error: ' + error);
});
}
With this service, we can now call getUsers() and use the resulting promise to get at the users once they're loaded:
userService.getUsers().then(function(userList) {
console.log(userList);
})
And with that you have tamed the asynchronous beast. Well.... for now at least. This will keep confusing even seasoned JavaScript developers once in a while, so don't worry if it takes some time to get used to.
Use async and await
Now that the function returns a promise, you can use async/await to make the final call from above look a bit more familiar:
function getAndLogUsers() async {
const userList = await userService.getUsers();
console.log(userList);
}
You can see that this code looks almost like a synchronous call, thanks to the use of the await keyword. But to be able to use that, we have to mark the getAndLogUsers (or whatever the parent scope of where we use await) as async, which means that it returns a Future too. So anyone calling getAndLogUsers will still need to be aware of its asynchronous nature.
You need to think async:
.service('userService', [function() {
this.getUsers = function() {
var users;
var ref = firebase.database().ref('/users/');
<!-- this happens ASYNC -->
ref.once('value', function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
},function(error){
alert('error: ' + error);
});
<!-- end async action -->
console.log(users); // outputs 'undefined'
}
}]);
I bet if you did this, you'd be fine:
.service('userService', [function() {
this.getUsers = function() {
var users;
var ref = firebase.database().ref('/users/');
<!-- this happens ASYNC -->
ref.once('value', function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
},function(error){
alert('error: ' + error);
});
<!-- end async action -->
window.setTimeout( function() {
console.log(users); // outputs 'undefined'
}, 1500 );
}
}]);
Clarification per Franks's comment:
I should clarify further that the setTimeout would just prove that this is an issue of timing. If you use setTimeout in your app, you're probably going to have a bad time as you can't reliably wait n milliseconds for results, you need to get them then fire a callback or resolve a promise after you have gotten the snapshot of the data.
I have a file called db.js from which I make all my firebase calls.
I am calling a function in db.js from another file called home.js
How do I make it that the firebase connection stays open and the data gets passed back to home.js? I can't use a promise because that closes the connection.
Here is the function from db.js:
export function getShopNames() {
let userID = auth.currentUser.uid
let stores = []
userDB.ref('/users/' + userID + "/stores").on('value', snap => {
snap.forEach(storeNames => {
stores.push(storeNames.key)
})
return stores
})
}
and I call it from home like this:
let stores = db.getShopNames()
I want it to work so if a new store gets added to the real-time database, the variable updates
There is no concept of file based scope in JavaScript. The listener will stay active from the moment you call on('value', until you either call off on that same location or until you load a new page.
But your return stores doesn't do anything meaningful right now. It returns a value from the callback function that nobody will ever see/use.
Data is loaded from Firebase asynchronously, which means you can't return it from a function in the normal way. By the time the return statement runs, the data hasn't loaded yet. That's why you'll usually return a so-called promise, which then resolves when the data has loaded.
In your function that'd be:
export function getShopNames() {
return new Promise(function(resolve, reject) {
let userID = auth.currentUser.uid
let stores = []
userDB.ref('/users/' + userID + "/stores").once('value', snap => {
snap.forEach(storeNames => {
stores.push(storeNames.key)
})
resolve(stores);
}, (error) => {
reject(error);
})
}
Now you can call this function like this:
getShopNames().then((shopnames) => {
console.log(shopnames);
})
Update: you commented that you also want to handle updates to the shop names, you can't use once() and can't use promises (since those only resolve once).
Instead pass in a custom callback, and invoke that every time the shop names change:
export function getShopNames(callback) {
let userID = auth.currentUser.uid
let stores = []
userDB.ref('/users/' + userID + "/stores").once('value', snap => {
snap.forEach(storeNames => {
stores.push(storeNames.key)
})
callback(stores);
})
}
And then call it like:
getShopnames(function(shopnames) {
console.log(shopnames);
});
I'm using Firebase along with angularJS to fetch a list of users. I can read all the users from my database with the once() function, but I can't figure out why userList returns undefined below
.service('userService', [function() {
this.getUsers = function() {
var users;
var userList;
var ref = firebase.database().ref('/users/');
ref.once('value').then(function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
}).then(function(){
userList = users;
console.log(userList); // outputs all users
},(function(error){
alert('error: ' + error);
});
console.log(userList); // outputs 'undefined'
}
}]);
I wait to assign my userList variable until I'm done processing users, but no luck.
There is something important here that I am missing where Promises/callbacks are concerned and I cannot find it in the documentation; could someone help me understand my issue?
The problem is (as imjared says) that the data is read from Firebase asynchronously. So the code doesn't execute in the order that you think. It's easiest to see that by simplifying it with just a few log statements:
.service('userService', [function() {
this.getUsers = function() {
var ref = firebase.database().ref('/users/');
console.log("before attaching listener");
ref.once('value').then(function(snapshot) {
console.log("got value");
});
console.log("after attaching listener");
}
}]);
The output of this will be:
before attaching listener
after attaching listener
got value
Knowing the order in which this executes should explain perfectly why you cannot print the user list after you've just attached the listener. If not, I recommend also reading this great answer: How to return the response from an asynchronous call
Now for the solution: you will either need to use the user list in the callback or return a promise.
Use the user list in the callback
This is the oldest way to deal with asynchronous code: move all code that needs the user list into the callback.
ref.once('value', function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
}
console.log(users); // outputs all users
})
You're reframing your code from "first load the user list, then print its contents" to "whenever the user list is loaded, print its contents". The difference in definition is minor, but suddenly you're perfectly equipped to deal with asynchronous loading.
You can also do the same with a promise, like you do in your code:
ref.once('value').then(function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
});
But using a promise has one huge advantage over using the callback directly: you can return a promise.
Return a promise
Often you won't want to put all the code that needs users into the getUsers() function. In that case you can either pass a callback into getUsers() (which I won't show here, but it's very similar to the callback you can pass into once()) or you can return a promise from getUsers():
this.getUsers = function() {
var ref = firebase.database().ref('/users/');
return ref.once('value').then(function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
return(users);
}).catch(function(error){
alert('error: ' + error);
});
}
With this service, we can now call getUsers() and use the resulting promise to get at the users once they're loaded:
userService.getUsers().then(function(userList) {
console.log(userList);
})
And with that you have tamed the asynchronous beast. Well.... for now at least. This will keep confusing even seasoned JavaScript developers once in a while, so don't worry if it takes some time to get used to.
Use async and await
Now that the function returns a promise, you can use async/await to make the final call from above look a bit more familiar:
function getAndLogUsers() async {
const userList = await userService.getUsers();
console.log(userList);
}
You can see that this code looks almost like a synchronous call, thanks to the use of the await keyword. But to be able to use that, we have to mark the getAndLogUsers (or whatever the parent scope of where we use await) as async, which means that it returns a Future too. So anyone calling getAndLogUsers will still need to be aware of its asynchronous nature.
You need to think async:
.service('userService', [function() {
this.getUsers = function() {
var users;
var ref = firebase.database().ref('/users/');
<!-- this happens ASYNC -->
ref.once('value', function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
},function(error){
alert('error: ' + error);
});
<!-- end async action -->
console.log(users); // outputs 'undefined'
}
}]);
I bet if you did this, you'd be fine:
.service('userService', [function() {
this.getUsers = function() {
var users;
var ref = firebase.database().ref('/users/');
<!-- this happens ASYNC -->
ref.once('value', function(snapshot) {
users = snapshot.val();
for(var key in users) {
users[key].id = key;
// do some other stuff
}
console.log(users); // outputs all users
},function(error){
alert('error: ' + error);
});
<!-- end async action -->
window.setTimeout( function() {
console.log(users); // outputs 'undefined'
}, 1500 );
}
}]);
Clarification per Franks's comment:
I should clarify further that the setTimeout would just prove that this is an issue of timing. If you use setTimeout in your app, you're probably going to have a bad time as you can't reliably wait n milliseconds for results, you need to get them then fire a callback or resolve a promise after you have gotten the snapshot of the data.
I am trying to assign callback function values and display ng-repeat
var myApp = angular.module('mybet',[]);
myApp.controller('MyBetController', function($scope,$firebase,ProfileFactory,MybetFactory){
$scope.myBetEvents = MybetFactory.getUsersBetsProfile(currentLoggedUser, function(betsData){
//Callback retuning null value here
$scope.myBetEvents = betsData;
});
});
myApp.factory('MybetFactory', ['$firebase', function($firebase){
var factory = {};
factory.getUsersBetsProfile = function(userId, callback){
var firebaseRef = new Firebase("https://xxx.firebaseio.com/usersbetsprofile").child(userId);
var firebaseBetsRef = new Firebase("https://xxx.firebaseio.com/events");
var userBettedEvent = [];
//retrive the data
firebaseRef.on('value', function (snapshot) {
console.log(snapshot.val());
var data = snapshot.val();
if (data){
//callback(data);
angular.forEach(data, function(data){
console.log('For Each getUsersBets',data);
var firebaseEventsData = firebaseBetsRef.child(data.event_id);
//retrive the data of bets
firebaseEventsData.on('value', function (snapshot) {
userBettedEvent.push(snapshot.val());
console.log('userBettedEvent',userBettedEvent);
//if I call callback here then i get the values but calling callback multipul time is no the option
}, function (errorObject) {
console.log('The read failed: ' + errorObject.code);
});
});
//ISSUE:: callback returing null values
callback(userBettedEvent);
}else{
console.log('Users has no bets');
}
}, function (errorObject) {
console.log('The read failed: ' + errorObject.code);
});
};
return factory;
}])
View::
<ul class="list">
<li class="item" ng-repeat="events in myBetEvents">{{events.event_name}}</li>
</ul>
How can get callback() to return values and display in view? what is causing callback after foreach returning null?
As #coder already said in the comments, your callback(userBettedEvent) is in the wrong place. Where you have it now, it will be invoked before the on('value' from Firebase has completed.
From the comments it is clear that you've notice that, yet only want the callback to invoked for one child. You can simply use a limit for that.
Something like this would work:
if (data){
data.limit(1).on('value', function(childData) {
var firebaseEventsData = firebaseBetsRef.child(childData.event_id);
firebaseEventsData.on('value', function (snapshot) {
userBettedEvent.push(snapshot.val());
callback(userBettedEvent);
}
})
I haven't really tested the code, but I'm quite sure a limit should get you in the right direction.
As a side note: you seem to be using Firebase as a traditional database, pulling the data out like you'd do with SQL. As you may notice: that is not a natural model for Firebase, which was made to synchronize data changes. If you want to stick to pulling the data, you may consider using once('value' instead of on('value' to ensure the handlers fire only once for every time you try to pull the data from Firebase.