onSnapshot, forEach, and get() synchronously on Firebase - javascript

I am using firebase's .onSnapshot to grab the ID of the users currently online, and store each ID to an array. I successfully deployed .onSnapshot to get the ID of the online users, but I return an empty array at the end
var learning_language;
db.collection(ll_profile).doc(user_uid).get().then(function(doc) {
learning_language = doc.data().learning_language;
})
db.collection(ns_status).where("state", "==", "online").onSnapshot(function(snapshot) {
var ns_match = [ ];
snapshot.forEach(function(userSnapshot) {
db.collection("ns_profile").doc(userSnapshot.id).get().then(function(doc) {
spoken_language = doc.data().spoken_language;
if (learning_language == spoken_language) {
ns_match.push(userSnapshot.id);
console.log(ns_match);
}
})
})
return (ns_match);
What I am trying to do is to first define the learning_language retrieved from the collection ll_profile with the current user's ID named user_uid.
Then .onSnapshot listens to another group of users' online state (which automatically updates if an user is online or offline) inside ns_status collection. After, the returned online user from .onSnapshot is checked if the spoken_language field inside their document (named with their corresponding uid) matches with learning_language defined earlier. If it matches, then store the uid into the array of ns_match.
The values inside ns_match are correct. I think .get() executes asynchronously. That is why ns_match is returned empty.
How should I return ns_match at the end with all the values stored properly?
Thanks in advance.

function getMatches() {
return new Promise(resolve => {
db.collection(ll_profile).doc(user_uid).get()
.then(function(doc) {
var learning_language = doc.data().learning_language;
db.collection(ns_status)
.where("state", "==", "online")
.onSnapshot(function(snapshot) {
var ns_match = [];
snapshot.forEach(function(userSnapshot) {
db.collection("ns_profile")
.doc(userSnapshot.id)
.get()
.then(function(doc) {
spoken_language = doc.data().spoken_language;
if (learning_language == spoken_language) {
ns_match.push(userSnapshot.id);
console.log(ns_match);
}
});
});
resolve(ns_match);
});
});
});
}
getMatches().then(ns_matches => console.log(ns_matches));

wrapping in a promise is the correct move. However, remember that snapshot returns metadata about your result. Particularly, snapshot.size. One can use that value to count records, inside the foreach method, or compare the destination array length with the snapshot.size value

Related

Unable to add object fetched from mongoDB to normal javascript array

I am trying to create an add to cart button which fetches the data from product database using the id of specific product which I selected. I am trying to push the object found using the same Id into a normal javascript array and then to display it using ejs methods. While I was tring I found I am unable to push the data in object form.
Summary:
On 7th line I have declared an array and in that array I want to store some objects which I have fetched frome a db model.
On 15th line I am trying to push the object form into my array so that I could iterate through the objects to display them on my page using ejs. But I am unable to do that.
screenshots:
Here's the final result I'm getting even after trying to push objects in array:
empty array logged
Here are the objects I'm trying to push:
Objects
Code:
app.get("/cart", (req, res) => {
if (req.isAuthenticated()) {
const findcartdata = req.user.username;
userData.findOne({email: findcartdata}, (err, BookId) => {
// console.log(BookId.cartItemId);
const idArray = BookId.cartItemId;
var bookArray = [];
idArray.forEach((data) => {
productData.findOne({_id: data}, (err, foundBookData) =>{
// console.log(foundBookData);
if(err){
console.log(err);
}
else{
bookArray.push(foundBookData);
}
})
});
console.log(bookArray);
// res.render("cart", {
// cartBookArray: BookId.cartItemId
// })
});
} else {
res.redirect("/login");
}
})
In above code i found the user's email using passport authentication user method and using that email I wanted to add the products in a different javascript array (which I am goint to pass to my ejs file of cart and then iterate it on list) using those array of Id which I got from another model called userData. The problem is I am able to find userData of each Id but unable to store them as an array of objects.
Looks like a timing issue, your code completes before the database downloads the objects and pushes them to your array.
This should fix your issue:
// ...
const idArray = BookId.cartItemId;
var bookArray = [];
for (const data of idArray) {
const foundBookData = await productData.findOne({_id: data}).catch(console.error);
if (!foundBookData) continue;
bookArray.push(foundBookData);
}
console.log(bookArray);
// ...
By the way, make sure to make the whole function asynchronous as well, which would be done by changing this line:
userData.findOne({email: findcartdata}, async (err, BookId) => { // ...

Firebase real time database access data array from deleted node

I am deleting a FRTDB node, I want to access deleted data from that node. the functions looks as follow:
exports.events = functions.database.ref('/events/{eventId}').onWrite(async (change, context) => {
const eventId = context.params.eventId
if (!change.after.exists() && change.before.exists()) {
//data removed
return Promise.all([admin.database().ref(`/events/${eventId}/dayofweek`).once('value')]).then(n => {
const pms = []
const days = n[0]
days.forEach(x => {
pms.push(admin.database().ref(`${change.before.val().active ? 'active' : 'inactive'}/${x.key}/${eventId}`).set(null))
})
return Promise.all(pms)
});
else {
return null;
}
})
The probem I am having is that
admin.database().ref(`/events/${eventId}/dayofweek
do not loop the data because it seems data is no longer there so the forEach is not working. How can I get access to this data and get to loop the deleted data?
Of course you won't be able to read data that was just deleted. The function runs after the delete is complete. If you want to get the data that was just deleted, you're supposed to use change.before as described in the documentation:
The Change object has a before property that lets you inspect what was
saved to Realtime Database before the event. The before property
returns a DataSnapshot where all methods (for example, val() and
exists()) refer to the previous value. You can read the new value
again by either using the original DataSnapshot or reading the after
property. This property on any Change is another DataSnapshot
representing the state of the data after the event happened.
The data that was deleted from the database is actually included in the call to your Cloud Function. You can get if from change.before.
exports.events = functions.database.ref('/events/{eventId}').onWrite(async (change, context) => {
const eventId = context.params.eventId
if (!change.after.exists() && change.before.exists()) {
//data removed
days = change.before.val().dayofweek;
...
})

how to get items from array on respectively

I have an array contain unique keys, these keys I want to take it to get his Data from firebase real-time DB
the array like this
["dtnMG3dVEQXL1TPct3awl927jax2", "y5eAQppoEZRELsDL3hLbY5b0A763"]
so I iterate it using forEach Method to get a single key to check if found in the users tree or not But the result is null?
But when I Log the keys.forEach(key => console.log(key)) I can Get every key in a single line
firebase
.database()
.ref("users")
.child(keys.forEach(key => key))
.once("value")
.then(users => {
// let username = users.val().username;
console.log(users.val());
// Object.assign(users, { [Object.keys(users)]: username });
});
Edit
So thats fine and get every single username, Now the new issues is i want to save these names to other object i have
users = {
dtnMG3dVEQXL1TPct3awl927jax2: // the unique key
-LmUSeyWtBPqcoN8l6fd: [{…}]
-LmUgcbdzElxWpLkN-F9: [{…}]
// here's i want to add somthing like thies
-username : "userOne" //I got it from the Looping
y5eAQppoEZRELsDL3hLbY5b0A763:
-LmSTYlxa7bjy0Beazmy: [{…}]
-LmUad3lvkPdTQDFo4Ds: [{…}]
-LmUyDmGcEmYKOJnvEiA: [{…}]
// here's i want to add somthing like thies
-username : "userTwo" // I got it from the Looping
}
keys.forEach(key => key)
This return every element of the array keys. And you are passing these elements to the child function. However, child functions expects only one argument.
Try this:
for( let key of keys){
firebase
.database()
.ref("users")
.child(key)
.once("value")
.then(users => {
// let username = users.val().username;
console.log(users.val());
// Object.assign(users, { [Object.keys(users)]: username });
});
}
Side not: Do not call async function inside forEach loop.
.child(keys.forEach(key => key)) - This is your problem. This forEach effectively returns all your array values one by one. But you call it inside a function with one argument that gets called one time. So it will only check for the first value in your array and the rest will be dumped, so its like looking inside a 1-element array.
Solution is to perform the DB calls inside the iteration:
keys.forEach( key => {
firebase
.database()
.ref("users")
.child(key)
.once("value")
.then(users => {
// let username = users.val().username;
console.log(users.val());
// Object.assign(users, { [Object.keys(users)]: username });
}});
EDIT:
to do what you wanted after you edited, assuming the variable curUser holds the username you want to insert and curKey is the key associated to that username, just: users[key]["username"] = curUser;. This will reference the property which is named after curKey in the users object, and will create a "username" property in it and store the value there.
Notice that if you already have "username" property in this key object it will be overriden.
** I assumed the data structure of the key-properties inside "users" are objects even tho your code doen't really show it.

Can I treat items found through a Promise.all as a firebase collection?

I am stuck in what I thought was a very simple use case: I have a list of client ids in an array. All I want to do is fetch all those clients and "watch" them (using the .onSnapshot).
To fetch the client objects, it is nice and simple, I simply go through the array and get each client by their id. The code looks something like this:
const accessibleClients = ['client1', 'client2', 'client3']
const clients = await Promise.all(
accessibleClients.map(async clientId => {
return db
.collection('clients')
.doc(clientId)
.get()
})
)
If I just needed the list of clients, it would be fine, but I need to perform the .onSnapshot on it to see changes of the clients I am displaying. Is this possible to do? How can I get around this issue?
I am working with AngularFire so it is a bit different. But i also had the problem that i need to listen to unrelated documents which can not be queried.
I solved this with an object which contains all the snapshot listeners. This allows you to unsubscribe from individual client snapshots or from all snapshot if you do not need it anymore.
const accessibleClients = ['client1', 'client2', 'client3'];
const clientSnapshotObject = {};
const clientDataArray = [];
accessibleClients.forEach(clientId => {
clientSnapshotArray[clientId] = {
db.collection('clients').doc(clientId).onSnapshot(doc => {
const client = clientDataArray.find(client => doc.id === client.clientId);
if (client) {
const index = clientDataArray.findIndex(client => doc.id === client.clientId);
clientDataArray.splice(index, 1 , doc.data())
} else {
clientDataArray.push(doc.data());
}
})
};
})
With the clientIds of the accessibleClients array, i create an object of DocumentSnapshots with the clientId as property key.
The snapshot callback function pushes the specific client data into the clientDataArray. If a snapshot changes the callback function replaces the old data with the new data.
I do not know your exact data model but i hope this code helps with your problem.

Get the ChildrenCount of a child in Firebase using JavaScript

I have been doing this for an hour. I simply want to get the number of children in the child "Success" in the database below. The answers in similar stackoverflow questions are not working. I am new in Javascript Programming.
So far I have tried this
var children = firebase.database().ref('Success/').onWrite(event => {
return event.data.ref.parent.once("value", (snapshot) => {
const count = snapshot.numChildren();
console.log(count);
})
})
and also this
var children = firebase.database().ref('Success/').onWrite(event => {
return event.data.ref.parent.once("value", (snapshot) => {
const count = snapshot.numChildren();
console.log(count);
})
})
Where might I be going wrong.
As explained in the doc, you have to use the numChildren() method, as follows:
var ref = firebase.database().ref("Success");
ref.once("value")
.then(function(snapshot) {
console.log(snapshot.numChildren());
});
If you want to use this method in a Cloud Function, you can do as follows:
exports.children = functions.database
.ref('/Success')
.onWrite((change, context) => {
console.log(change.after.numChildren());
return null;
});
Note that:
The new syntax for Cloud Functions version > 1.0 is used, see https://firebase.google.com/docs/functions/beta-v1-diff?authuser=0
You should not forget to return a promise or a value to indicate to the platform that the Cloud Function execution is completed (for more details on this point, you may watch the 3 videos about "JavaScript Promises" from the Firebase video series: https://firebase.google.com/docs/functions/video-series/).
const db = getDatabase(app)
const questionsRef = ref(db, 'questions')
const mathematicalLiteracy = child(questionsRef, 'mathematicalLiteracy')
onValue(mathematicalLiteracy, (snapshot) => {
const data = snapshot.val()
const lenML = data.length - 1
console.log(lenML)
})
This method worked for me. I wanted to get the children's count of the mathematicalLiteracy node in my database tree. If I get its value using .val() it returns an array that contains that node's children and an extra empty item. So, I subtracted that one empty item's count. Finally, I get my needed children's count.

Categories

Resources