How to retrieve data from Firebase in Javascript? - 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.

Related

trying to fix the problems arising from asynchronous code in 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.

Firebase concurrency issue: how to prevent 2 users from getting the same Game Key?

DATABASE:
SITUATION:
My website sells keys for a game.
A key is a randomly generated string of 20 characters whose uniqueness is guaranteed (not created by me).
When someone buys a key, NTWKeysLeft is read to find it's first element. That element is then copied, deleted from NTWKeysLeft and pasted to NTWUsedKeys.
Said key is then displayed on the buyer's screen.
PROBLEM:
How can I prevent the following problem :
1) 2 users buy the game at the exact same time.
2) They both get the same key read from NTWKeysLeft (first element in list)
3) And thus both get the same key
I know about Firebase Transactions already. I am looking for a pseudo-code/code answer that will point me in the right direction.
CURRENT CODE:
Would something like this work ? Can I put a transaction inside another transaction ?
var keyRef = admin.database().ref("NTWKeysLeft");
keyRef.limitToFirst(1).transaction(function (keySnapshot) {
keySnapshot.forEach(function(childKeySnapshot) {
// Key is read here:
var key = childKeySnapshot.val();
// How can I prevent two concurrent read requests from reading the same key ? Using a transaction to change a boolean could only happen after the read happens since I first need to read in order to know which key boolean to change.
var selectedKeyRef = admin.database().ref("NTWKeysLeft/"+key);
var usedKeyRef = admin.database().ref("NTWUsedKeys/"+key);
var keysLeftRef = admin.database().ref("keysLeft");
selectedKeyRef.remove();
usedKeyRef.set(true);
keysLeftRef.transaction(function (keysLeft) {
if (!keysLeft) {
keysLeft = 0;
}
keysLeft = keysLeft - 1;
return keysLeft;
});
res.render("bought", {key:key});
});
});
Just to be clear: keyRef.limitToFirst(1).transaction(function (keySnapshot) { does not work, but I would like to accomplish something to that effect.
Most depends on how you generate the keys, since that determines how likely collisions are. I recommend reading about Firebase's push IDs to get an idea how unique those are and compare that to your keys. If you can't statistically guarantee uniqueness of your keys or if statistical uniqueness isn't good enough, you'll have to use transactions to prevent conflicting updates.
The OP has changed the question a bit so, i will update the answer as follows: I will leave the bottom part about transactions as it was and will put the new update on top.
I can see two ways to proceed:
1) handle the lock system on your own and use JavaScript callbacks or other mechanisms for preventing simultaneous access to a portion of the code.
or
2) Use transactions/fireBase. On this case, i don't have the setup ready to share code other than sample/pseudo code provided at the bottom of this page.
With respect to option 1 above:
I have coded a use-case and put in on plunker. It uses JavaScript callbacks to queue users as they try to access the part of the code under lock.
I. user comes in and he is placed in queue
II. It then calls the callback function which pops users as
first come first out bases. I have the keys on top of the page to
be shared by the functions.
I have a button click event to this and when you click the button twice quickly, you will see keys assigned and they're different keys.
To read this code, click on the script.js file on the left and read starting from the bottom of the page where it calls the functions.
Here is the sample code in plunker. After clicking it, click on Run on top of the page and then click on the button on right hand side. Alert will pop up to show which key is given (note, there are two calls back to back to show two users coming in at same time)
https://plnkr.co/edit/GVFfvqQrlLeMaKlo5FCj?p=info
The fireBase transactions:
Use fireBase transactions to prevent concurrent read/write issues - below is the transaction() method signiture
transaction(dataToBeWritten, onComplete, applyLocally) returns fireBase.promise containing {
committed: boolean, nullable fireBase.database.snapshot }
Note, transaction needs writeOperation as first parameter and in your case looks like you’re removing a key upon success! hence the following function to be called in place of write
Try this pseudo code :
//first, get reference to your db
var selectedKeyRef = admin.database().ref("NTWKeysLeft/"+key);
// needed by transaction as first parameter
function writeOperation() {
selectedKeyRef.remove();
}
selectedKeyRef.transaction(function(writeOperation) , function(error,
committed, snapshot) {
  if (error) {
    console.log('Transaction failed abnormally!', error);
  } else if (!committed) {
    console.log('We aborted the transaction (because xyz).’);
  } else {
    console.log(‘keyRemoved!’);
  }
  console.log(“showKey: ", snapshot.val());
}); // end of the transaction() method call
Docs + to see parameters/return objects of the transaction() method see:
https://firebase.google.com/docs/reference/js/firebase.database.Reference#transaction
In the Docs.... If another client writes to the location before your new value is successfully written, your update function is called again with the new current value, and the write is retried.
https://firebase.google.com/docs/database/web/read-and-write#save_data_as_transactions
I don't think the problem you're worried about can happen. JavaScript, including Node, is single-threaded and can only do one thing at a time. If you had a big server infrastructure with more than one server running this code, then it would be possible, but for a single Node program, there's no problem.
Since none of the previous answers discussing the scope of Transactions worked out, I would suggest a different workaround.
Is it possible to trigger the unique code generation when someone buys a code? If yes, you could generate the unique string if the "buy" button is clicked, display the ID and save the ID to your database.
Later the user enters the key in your game, which checks if the ID is written in your database. This might probably also save a bit of data, since you do not need to keep track of the unique IDs before they get bought and you will also not run out of IDs, since they will always get generated when necessary.

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.

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.

Parse.com Add User Pointer to a class in before save

I have a table called Roster where it stores a User Pointer in one of the column. I am trying set that column in before save method when it is new.So far i have this but i am not sure how to get the user id
Parse.Cloud.beforeSave("Roster",function(request,response){
//Update roster with sender Id
if (request.object.isNew()){
var userPointer = /*NOT SURE HOW TO GET WHO SENT IT*/
request.object.set("User",userPointer);
}
response.success();
});
Any idea?
var userPointer = request.user;
Is the proper method. Todd's answer works on the client side, but not on cloud code, where beforeSave triggers occur.
If you need to access any of the user's information beyond their id, you'll have to first fetch the user, as the entire object is not sent in the request.
Edit - Just wanted to add that before/afterSave triggers have a 3 second timeout. This is enough time to perform a quick query or two, but if you have a lot of objects in your database, or perform many save/fetch/query calls, you may end up exceeding your 3 second limit. If you have a lot of that logic that needs to occur, rather than saving the object from the client, call a cloud code function that handles all of those changes, then saves the object and returns the newly saved object, so that you can set your client side object to the returned, up to date object.
As Jake T pointed out, you will need to use
var user = request.user
Parse.User.current() is not supported in a cloud code environment.

Categories

Resources