Firebase, retrieving data asynchronously - javascript

I wrote a promise in javascript to delay an animation until some data is retrieved from my firebase database. The problem is that for some reason the promise isn't working, and the code isn't running asynchronously. Am i not using this promise properly, or is there another issue?
var name = document.querySelector('.name')
new Promise (function() {
firebase.database().ref('users/' + userId ).on('value', function(snap) {
console.log('first');
name.innerText = snap.child('name').val();
});
}).then(console.log('second'));
My issue is that when I check the console, I find that the code isn't running asynchronously, and I will see the messages logged in the wrong order, 'second' then 'first'.

The first problem is that (as I commented on cartant's answer) an on() handler can be invoked multiple times and thus cannot be treated as a promise.
If you only care about the current value of the node, you can use once(), which does return a promise:
var name = document.querySelector('.name')
firebase.database().ref('users/' + userId ).once('value', function(snap) {
console.log('first');
name.innerText = snap.child('name').val();
}).then(function() { console.log('second') });
Alternatively, if you do care about changes in the value too, you may want to keep on using on(). But in that case you should pass in a callback.
var name = document.querySelector('.name')
firebase.database().ref('users/' + userId ).on('value', function(snap) {
console.log('first');
name.innerText = snap.child('name').val();
});
If you want to explicitly detect the first value in this, do something like:
var name = document.querySelector('.name'),
isFirst = true;
firebase.database().ref('users/' + userId ).on('value', function(snap) {
console.log('first');
name.innerText = snap.child('name').val();
if (isFirst) {
console.log('second');
}
isFirst = false;
});

Related

Chart showing just after resizing window - Using chart.js and Firebase database [duplicate]

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.

Unable to access Firebase database data outside of function in Vue [duplicate]

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.

Firebase Function returning Undefined values, even with promises

I'm new on Firebase, I've a simple program that fetches product information using "ProductID".
Here's the hierarchy:
PRODUCT CHILD
The problem is, when I use this function, it returns undefined values. I know it's asynchronous, and I've tried adding promises, but still in vain. Here's it:
function getProduct(prID){
var db = admin.database();
var ref = db.ref("server/products/" + prID);
let rData = '';
ref.on("value", function(snapshot) {
rData = snapshot.val();
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
return rData;
Calling the same function with same values returns the product, but calling for different parameter values, it returns undefined.
I've tried using (in a function getProductPromise(prID) ), I changed above function to:
function getProductPromise(id) {
var db = admin.database();
var ref = db.ref("server/products/" + id);
let rData = '';
return ref.once('value').then(function(snapshot) {
return snapshot.val();
});
}
it is still returning undefined values. What could be the possible way to ensure that some values are returned.
Thanks.
Looks like you're having some scoping issues in the first example, so not sure if it would ever work as you're expecting. Promises are your best bet. You can try something like:
function fetchProductById (id) {
var db = admin.database()
var collectionRef = db.ref('server/products')
var ref = collectionRef.child(id)
return ref.once('value')
.then((snapshot) => {
return snapshot.val()
})
}
// then to implement:
fetchProductById('abc')
.then((result) => {
console.log('the result is', result)
})

promises only working properly with then function

I have a button which executes a function with a promise that gets and displays data from firebase in html (I'm using angularJS, ionic and firebase).
The problem is : if I don't inlclude a .then(function(){}) after it, the promise gets executed in unsynchronous way, meaning I have to click the button once again so the data gets displayed in html.
I want to put the data in the scope after the promise (that gets the data from firebase), but for some reason in only works if I put a .then function after it.
However, the data gets displayed normally in the console, but not in html (meaning I think that the function doesn't get attached to the scope).
Here is the piece of code :
$scope.displayChat = function () {
var userId = firebase.auth().currentUser.uid; // Get ID
var deferred = $q.defer()
var db = firebase.database();
var ref = db.ref("12346787");
ref.on("value", function(snapshot) {
console.log(snapshot.val());
$scope.chatArray = snapshot.val();
deferred.resolve()
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
})
return deferred.promise.then(function(){
// Removing this empty .then(function(){}) function
// will result in asynchronousity.
// just "return deferred.promise;" doesn't work.
})
}
Any solutions? I don't have much experience with promises but I didn't find anything related. Cheers.
the purpose of promise is to manage asynchronous methods, so I don't really understand the problem...
Moreover normally the code inside displayChat must be executed, only the callback must be executed after. You should return the promise, that enable you to execute callback once you are sure the required asynchronous method are done.
When changes to scope are done by events external to the AngularJS framework, the framework needs to do an $apply to initiate a digest cycle to update the DOM.
(source: angularjs.org)
The .then method of a $q service promise automatically initiates the necessary digest cycle. In this case, the promise returned by the displayChat function is discarded and no digest cycle gets initiated. The subsequent click of the button initiates the digest cycle.
In the future, someone might wish to chain from the promise returned by the displayChat function. I recommend making the function more versatile by returning a proper promise and moving any changes to scope into a .then method.
$scope.displayChat = function () {
var userId = firebase.auth().currentUser.uid; // Get ID
var deferred = $q.defer()
var db = firebase.database();
var ref = db.ref("12346787");
//ref.on("value", function(snapshot) {
//USE once
ref.once("value", function(snapshot) {
console.log(snapshot.val());
//$scope.chatArray = snapshot.val();
deferred.resolve(snapshot.val());
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
//ADD rejection case
deferred.reject(errorObject);
})
return deferred.promise.then(function(chatArray){
//Move scope changes here
$scope.chatArray = chatArray;
//RETURN to chain data
return chatArray;
// Removing this empty .then(function(){}) function
// will result in asynchronousity.
// just "return deferred.promise;" doesn't work.
})
}
Also to avoid memory leaks, use ref.once instead of ref.on and be sure to reject the promise in the error case.
Promises are used to defer execution of some logic until the promise has been satisfied - ie: you have received your results from the database. In your case, you are already deferring your console display and setting of the $scope variable within ref.on. The promise code is redundant.
The fact that your result shows in the console proves you have received the result. When you update data in the scope, it will not appear until a digest cycle has occurred. Most of the time, Angular can automatically figure out when it needs to run a digest cycle. On those times when it does not, you can force it by wrapping your scope related logic in a timeout, in which case, your code would look as follows:
$scope.displayChat = function () {
var userId = firebase.auth().currentUser.uid; // Get ID
var db = firebase.database();
var ref = db.ref("12346787");
ref.on("value", function(snapshot) {
console.log(snapshot.val());
$timeout(function () {
$scope.chatArray = snapshot.val();
});
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
})
}
Your use of the promise .then method just happened to trigger a digest cycle. The promise itself really wasn't doing anything.
If your intention was to pass the snapshot back to the caller when it became available, that's when the promise comes into play, and would be done as follows:
$scope.displayChat = function () {
var userId = firebase.auth().currentUser.uid; // Get ID
var deferred = $q.defer()
var db = firebase.database();
var ref = db.ref("12346787");
ref.on("value", function(snapshot) {
deferred.resolve(snapshot)
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
})
return deferred.promise;
};
$scope.callDisplayChat = function () {
$scope.displayChat().then(function (result) {
$scope.chatArray = result.val();
});
};

Why Does Firebase Lose Reference outside the once() Function?

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.

Categories

Resources