trying to fix the problems arising from asynchronous code in javascript - javascript

I am new in database systems and what I am trying to do is to check whether the e-mail entered by the user during login exists in the database or not. I use Firebase Databse. So, the code I have written is this:
function login(){
var e_mail = document.getElementById("e-mail").value;
rootRef = firebase.database().ref();
rootRef.orderByChild("E_mail").on("child_added", function(snapshot){
lst.push(snapshot.val().E_mail);
//console.log(lst);
})
console.log(lst);
}
let lst = [];
login_btn.onclick = function() {login()};
I want to fetch all e-mails from the database, add them in the list and then loop through that list. Maybe this is not the best way, but that's what I'm working on. I could also just say if (snapshot.val().E_mail == e_mail){alert("there is such a user");}but the problem I have encountered and want to deal with is not that, it's the "callback" function inside login function. When I console the list in the outer function it shows an empty list as it does not run the inner function until it is done with the outer one. I understand this. But how can I avoid or fix this. I want to get the full list of e-mails to be able to loop through it then. Also, I don't know how to end the "loop" in Firebase, because it is sort of looping when it gets the e-mails. So I would like to stop at the moment when it finds a matching e-mail.

You're downloading all users to see if one name exists already. That is a waste of bandwidth.
Instead you should use a query to match the email you're looking for, and only read that node:
rootRef.orderByChild("E_mail").equalTo(e_mail).once("value", function(snapshot){
if (snapshot.exists()) {
// A node with the requested email already exists
}
})
In general, if you need to process all nodes, you'll want to use a value event, which executes for all matching nodes at once. So to get all users from the database, add them to a list, and then do something with that list:
rootRef.orderByChild("E_mail").once("value", function(snapshot){
var list = [];
snapshot.forEach(function(childSnapshot) {
list.push(childSnapshot.val());
});
console.log(list); // this will display the populated array
})
Note that you won't be able to access the list outside of the callback. Even if you declare the variable outside of the callback, it will only be properly populated inside the callback. See Xufox' comment for a link explaining why that is.

Related

How to retrieve data from Firebase in Javascript?

I have one node named as group_chat and another one named as users. The node group_chat contains the children which contains message, time and sender_id. Whereas the node users contains the details of users. When I retrieve chats from group_chat node, I also want to display the information of each sender. On using this code
rootChat.on("child_added", function (snapshot) {
if(snapshot.val().sender_id == logged_in_user_id) {
// Message by You
// Print snapshot.val().message
}
else{
const refUser = "/users/"+snapshot.val().sender_id;
const rootUser = database.ref(refUser);
rootUser.once("value", function(snap) {
// Print snap.val().name
// Display snap.val().picture
// Print snapshot.val().message
});
}
});
The problem is, when the code goes in else condition where it's the message of the other user, it retrieves the information of the user by going through the specific node. But while doing it, it automatically goes to next child while going through the user detail of first child which automatically spoils the arrangement of messages showing latest message in the middle, middle one in end and so on.
If I have 4 children named as -child1, -child2, -child3 and -child4 in node group_chat, while checking the -child1, it will also check -child2 and sometimes it will print -child 2 before -child1.
How can I resolve it? Is there any way where I can wait for the else condition to finish and then go to next child? Or is there any way where I can get user detail in just one line of code somehow like this
// rootUser.child.name
How about pre-loading the users, before you start building the HTML?
The outline of that would be something like:
Use on("value" instead of on("child_added, so that you get all messages in one go.
Loop over the messages to determine the unique user IDs.
Load all users with once("value") calls. Note that once() returns a promise, so you can wait to all users to be loaded with Promise.all().
Now loop over all messages again to build the HTML.
At this point if you need user data, you can look it up without needing an asynchronous call.

How to run multiple bots on one firebase database?

database = firebase.database();
var ref = database.ref('Users');
ref.orderByChild('state').equalTo("0").once('value', function(snapshot) {
var Users = snapshot.val();
i=0;
if (Object.keys(Users).length > 0){
getUser(Users);
} else {
console.log("No Users")
}
});
What I am doing is having a node js bot run through my database and search for users with state= 0. If state equals to zero, I run another script that goes and gets some information about them, updates their entry, and then changes the state to 1.
I have quite a large database, so it would be great if I could run a few instances of my bot. It won't work, though, because when the bots initially run, they all look at the same entries and remember which ones have a state = 0 and then they all repeat each other's work.
I tried changing the ref.orderByChild from using "once" to "on" child changed. That didn't seem to work though because it seemed as though the script was always waiting/listening for changes.. and never actually finished one loop. It does not move on to the next entry.
What's the best way to tackle something like this: having multiple bots being able to edit a firebase database without repeating each other's work?
Query and save all the data with a "master" script, then have it divvy up the entire thing and offload the split data to other scripts that receive their portion of data as input.

Firebase get all values of specific key

I have a users table on Firebase and each user has an email prop.
Structure looks like:
Users -> User UID (looks like n8haBbjgablobA2ranfuabu3aaaga2af) -> User Obj which includes email prop.
I'd like to get an array of all the users' emails (~1m).
How can I most efficiently do this?
Ps.:
I tried:
usersRef.startAt(0).endAt(20).once("value", function(snapshot) {
console.log('FIRST 20');
console.log(snapshot.val()); // null
});
But that fails.
Probably the most efficient approach in terms of data reads would be to denormalize your data. You could store the email addresses both in the individual user nodes and in an emailAddresses node. Then you could just query the emailAddresses node directly for your list of emails.
Still ~1m email address nodes would probably be too much all at once. I'd probably grab it in chunks... I'm guessing.
Update
"Grabbing in chunks" is essentially pagination. I would try to use something off the shelf before trying to roll my own pagination solution.
Pagination libraries to check out:
Firebase Utils Pagination: This is developed by Firebase, but they say it is experimental and not ready for production use. But, it's probably still worth messing around with.
firebase-paginator: this is developed by a community member and it seems pretty solid.
If you want to roll your own pagination, check out:
#kato's response in this StackOverflow answer He makes an interesting point about the potential problem with paginating a real time data set, but then provides some good starter code
Here's a good blog entry that talks about the code that I think is a part of the firebase-paginator library I linked to above
Everybody in their answers said that it was an easy thing, yet had no working solutions. Here's what I came up with:
usersRef.orderByChild('uid').limitToFirst(100).once('value', function (snapshot) {
var users = snapshot.val()
var uids = Object.keys(users);
var lastUid = uids[uids.length - 1];
// could be another property than uid, for example email, or username. Ps.: Consider that the last ID from the previous chunk will be duplicated.
usersRef.orderByChild('uid').startAt(lastUid).limitToFirst(100).once('value', function (snapshot) {
var users = snapshot.val()
var uids = Object.keys(users);
console.log(uids);
var lastUid = uids[uids.length - 1];
// re-run function until done
})
})
Since this is a one-time deal, an option is to simply iterate over each node in the parent 'data' node capturing the child data, stripping out the email address and dumping that to a file.
the event you want is
child_added: retrieve lists of items or listen for additions to a list
of items. This event is triggered once for each existing child and
then again every time a new child is added to the specified path. The
listener is passed a snapshot containing the new child's data.
and the code to iterate all of the child nodes in the data node is
var dataRef = firebase.database().ref('myRootRef/data');
datRef.on('child_added', function(data) {
//data.val() will contain the child data, such as the email address
//append it to a text file here (for example), save to disk etc.
});
The key here is that this event is triggered once for each child, similar to iterating over all of the indexes in an array.
This will retrieve each child and present it to your app, one child at a time, iterating over all the children within the node.
It's going to take a while with that many nodes to chew through.

Managing an Array of Likes in AngularJS Service

I have a like Button on Profile Page, On click of Like Button i want to maintain an array of like and store it into my db.
in profile Controller I have
$scope.likeProfile = UserService.likeProfile(loggedInUser,$state.params.userId);
In User Service I have
function likeProfile(likedBy,id){
var likeArray = [];
likeArray.push(likedBy);
ref.child("users").child(id).update({ likedBy: likeArray});
}
I just want to understand how I could not intialize likeArray everyTime LikeProfile Method is called. So that all likes are pushed into array.
I would do it like this, not sure though if this is what you want to achieve. Initialization should not be done within the function if you want to keep the result. This keeps the result within the scope of one user.
$scope.likeArray = [];
function likeProfile (likedBy, id) {
$scope.likeArray.push(likedBy);
ref.child("users").child(id).update({ likedBy: likeArray});
}
Otherwise, if you need overall likes you have to initialize the array with the previous values within the function, sth. like
likeArray = getLikesFromDB(); // however you access your db
likeArray.push(likedBy);
ref.child("users").child(id).update({ likedBy: likeArray });
Try to debug with the browser dev tools (e.g. in Chrome Tools > Developer Tools) and see what you send in the network tab. If your array only contains one value the whole array will be overwritten unless you write a custom update function that adds a value rather than replaces the value.

jQuery Deferred returns only last value in loop

So I'm trying to go through one Firebase database to find entries in the database matching a criteria. Therefore I'm using the deferred object of jQuery to handle the database calls.
Once I get a return value from this first database I want to get the user info from a second database for each of those values in the first db. Then the results are added to a JSON array
so its:
<search for value, find one>
<<<search other db for oher info>>>
<continue search for outer value>
But this only returns one value - although everything else is running fine (and the console logs all the info correct).
Here's the code:
function find(searchLocation, profileID) {
var requestUserData = {
data: []
};
var def = $.Deferred();
//This will be executed as long as there are elements in the database that match the criteria and that haven't been loaded yet (so it's a simple loop)
Ref.orderByChild("location").equalTo(searchLocation).on("child_added", function(snapshot) {
def.ressolve(snapshot.val().ID);
});
return def.promise();
};
I hope you guys have any ideas on what to do or how I could solve this. Thanks in advance!
Edit: upon further testing I discovered that this problem already exists in the outer loop - so only the first value is being returned. I think this is related to the posission of the resolve() method but I didn't find a posibility on how to change this behaviour.
Firebase is a real-time database. The events stream as changes occur at the server. You're attempting to take this real-time model and force it into CRUD strategy and do a GET operation on the data. A better solution would be to simply update the values in real-time as they are modified.
See AngularFire, ReactFire, or BackboneFire for an example of how you can do this with your favorite bindings framework.
To directly answer the question, if you want to retrieve a static snapshot of the data, you want to use once() callback with a value event, not a real-time stream from child_added:
Ref.orderByChild("location").equalTo(searchLocation).once("value", function(snapshot) {
def.resolve(snapshot.val());
});

Categories

Resources