Not able to access documents in a collection Firestore - javascript

I have an array of objects which comes from firestore all_categories.
let docID = getStoreID();
let all_categories = [];
db
.collection("STORES")
.doc(docID)
.collection("CATEGORIES")
.orderBy("CAT_ADDED_ON","desc")
.limit(5).get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
all_categories.push(doc.data())
})
})
When I console.log(all_categories) It shows it has 2 objects but when I try to iterate through them it shows undefiened.
Like this:
all_categories.forEach(category => {
console.log(category)
})
The console does not print anything.
What can be the problem?

Retrieving data from a database is a asynchronous process you can attach another then to it like this when the previous then is completed it will run the after one.
db.collection("STORES")
.doc(docID)
.collection("CATEGORIES")
.orderBy("CAT_ADDED_ON","desc")
.limit(5)
.get()
.then((querySnapshot) => { querySnapshot.forEach((doc) => { all_categories.push(doc.data())})
.then(()=>{ all_categories.forEach(category => { console.log(category) })
})

get() is asynchronous (returns a Promise) meaning that it will move on to another task before it finishes. The then() method returns a Promise. It takes up to two arguments: callback functions for the success and failure cases of the Promise. Therefore you have to do the following to be able to access the category elements:
let docID = getStoreID();
let all_categories = [];
db
.collection("STORES")
.doc(docID)
.collection("CATEGORIES")
.orderBy("CAT_ADDED_ON","desc")
.limit(5).get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
all_categories.push(doc.data());
all_categories.forEach(category => {
console.log(category)
})
})
})
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then

Related

Pulling a variable out of Promise when querying data from Firestore Firebase database - Javascript

let savedArrayUID = []; let savedArrayEmails = [];
function pullIt(emailSearch) {
db.collection(collectionName).where('email', '==', emailSearch).get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
savedArrayUID.push(doc.id);
savedArrayEmails.push(doc.data());
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
// saved. push(doc.id);
return savedArrayUID;
})
});
}
I can query the data from the database but cannot pull the variable out of the scope of the function.
I want to use this function to pass through emails to find info of their profile saved in my Database.
I really struggle to understand how Promiseses can help here. I have a feeling this is already solved, but I could not find an answer anywhere.
There's two steps to this:
Ensure that your data makes it out of your pullIt (as a promise).
Then call that pullIt correctly, waiting for the promise to resolve.
In code, you're missing a top-level return the pullIt code:
function pullIt(emailSearch) {
// 👇
return db.collection(collectionName).where('email', '==', emailSearch).get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
savedArrayUID.push(doc.id);
savedArrayEmails.push(doc.data());
})
return savedArrayUID; // 👈
});
}
And then when calling pullIt, you'll need to use either await or then, to ensure pullIt completed before you try to access the result.
So either:
pullIt("yourSearchTeam").then((results) => {
// TODO: use your results here
})
Or (in an async context):
const results = await pullIt("yourSearchTeam")
// TODO: use your results here

How to call a function after the promise has been resolved?

The following code is for my FCM function where I am listening to firestore the getting tokens before constructing payload to send. Every time its sent the system logs that the tokens are empty. How can I make sure its not empty when sending the fcm?
let functions = require('firebase-functions');
let admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification =functions.firestore.document('chatrooms/{chatRoomId}/messages/{messageId}')
.onWrite((snap, context) => {
let message = snap.after.data().messageBody;
let messageSender = snap.after.data().senderName;
let messageUserId = snap.after.data().userId;
let chatRoomId = context.params.chatRoomId;
let tokens = [];
let chatRoomRef = admin.firestore().collection("chatrooms").doc(chatRoomId);
return admin.firestore().runTransaction(t => {
return t.get(chatRoomRef)
.then(chatroom => {
let usersArray = chatroom.data().chatMembers;
usersArray.forEach(user_id => {
let userIdRef = admin.firestore().collection("tokens").doc(user_id);
return t.get(userIdRef).then(doc => {
if (doc.exists) {
let user_token = doc.data().token;
functions.logger.log('token: ', token);
tokens.push(user_token);
}
}).catch(err => {
functions.logger.error(err);
})
});
});
}).then(() => {
//The transaction has run successfully, we expect tokens array to not be empty
functions.logger.log("Construction the notification message.");
const payload = {
data: {
data_type: "data_type_chat_message",
title: "Tuchat",
message: message,
sender_id: messageUserId,
sender_name: messageSender,
chatRoom_id: chatRoomId
}
};
const options = {
priority: "high",
timeToLive: 60 * 60 * 24
};
return admin.messaging().sendToDevice(tokens, payload).catch(err => {
functions.logger.error(err);
});
}).catch(err => {
functions.logger.error('Transaction error: ', err);
})
});
Also before trying transactions it was returning empty tokens.
The problem is because of the way you're dealing with promises inside the forEach loop. The loop will not wait for promises to be resolved for the code inside it. It is currently just iterating as fast as possible over each user ID and not waiting for the queries to complete inside it. That means the code will continue before tokens can get populated.
You should instead collect each of the promises inside the loop and use Promise.all() to wait for the entire batch, then return the promise from that to indicate when all the work is complete. That will ensure that tokens contains everything you want before the next then in the chain is executed.
The general form of the code will be like this:
.then(() => {
const promises = []
collection.forEach(item => {
const promise = doSomeWork()
promises.push(promise)
})
return Promise.all(promises)
})
.then(() => {
// this will continue only after all promises are resolved
})
See also:
How to use promise in forEach loop of array to populate an object
Node JS Promise.all and forEach

How to avoid nesting promises with firebase transaction?

In an onDelete trigger I'm running a transaction to update some object. I now need to do some cleanup and delete some other objects before running that transaction. After adding the cleanup code I'm getting a warning about nesting promises which I don't know how to get rid of. Here is a snippet:
exports.onDeleteAccount = functions.firestore
.document('accounts/{accountID}')
.onDelete((account, context) => {
// First do the cleanup and delete addresses of the account
const query = admin.firestore().collection('account_addresses').where('accountID', '==', account.id);
return query.get().then(addresses => {
var promises = [];
addresses.forEach(address=>{
promises.push(address.ref.delete());
})
return Promise.all(promises);
}).then(()=> {
// Then run the transaction to update the account_type object
return runTransaction(transaction => {
// This code may get re-run multiple times if there are conflicts.
const acc_type = account.data().type;
const accountTypeRef = admin.firestore().doc("account_types/"+acc_type);
return transaction.get(accountTypeRef).then(accTypeDoc => {
// Do some stuff and update an object called users
transaction.update(accountTypeRef, {users: users});
return;
})
})
})
.catch(error => {
console.log("AccountType delete transaction failed. Error: "+error);
});
})
I don't think the problem comes from the Transaction but from the forEach loop where you call delete(). You should use Promise.all() in order to return a single Promise that fulfills when all of the promises (returned by delete()) passed to the promises array have been fulfilled, see below.
In addition, you do runTransaction(transaction => {...}) but runTransaction is a method of Firestore. You should do admin.firestore().runTransaction(...).
Therefore, the following should do the trick:
exports.onDeleteAccount = functions.firestore
.document('accounts/{accountID}')
.onDelete((account, context) => {
// First do the cleanup and delete addresses of the account
const query = admin.firestore().collection('account_addresses').where('accountID', '==', account.id);
return query.get()
.then(addresses => {
const promises = [];
addresses.forEach(address => {
promises.push(address.ref.delete());
})
return Promise.all(promises);
}).then(() => {
// Then run the transaction to update the account_type object
return admin.firestore().runTransaction(transaction => {
// This code may get re-run multiple times if there are conflicts.
const acc_type = account.data().type;
const accountTypeRef = admin.firestore().doc("account_types/" + acc_type);
return transaction.get(accountTypeRef).then(accTypeDoc => {
// Do some stuff and update an object called users
transaction.update(accountTypeRef, { users: users });
})
})
})
.catch(error => {
console.log("AccountType delete transaction failed. Error: " + error);
});
})

Promise.all returns [[PromiseValue]] but I need array of all returned values

I am trying to return array of json objects, but below code returns [[PromiseValue]] and data surely is there.
"results from function"
How can I get array of json objects and set it to state
NOTE: if I call .then(data => console.log(data)) inside getEndResults function that works but then I can't setState in there as it throws an error
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op.
getUserCredits = async (userId, creditId) => {
return await fetch(
`${someLink}/userId/${userId}/credit/${creditId}`,
{
method: "GET",
}
)
}
getEndResult = async () => {
const userId = await this.getUserId(userId);
const userData = await this.getUserData(userId);
const checkData = await Promise.all(userData.checks.map(check => {
return check.checkIds.map((checkIds) => {
return this.getUserCredits(userId ,checkIds)
.then(res => res.json())
.then(data => console.log(data))
// Can't setState here
// .then(data => this.setState({ results: data }))
})
}))
console.log(checkData);
}
You're creating a 2-dimensional array of promises, and then passing that into Promise.all. Promise.all only knows how to work with single-dimensional arrays, so what it sees is an array with things that aren't promises. For non-promises, promises.all just immediately resolves to the value it was given.
You will need to flatten out your 2-d array before sending it to Promise.all. If you have a polyfill for array.prototype.flatmap (which will soon be added to javascript, but isn't there yet), this can be done like:
const checkData = await Promise.all(userData.checks.flatMap(check => {
return check.checkIds.map((checkIds) => {
return this.getUserCredits(userId, checkIds)
.then(res => res.json());
});
If that function is not available to you, then you could write your own function for flattening a 2d array, something like this:
function flatten(arr) {
const result = [];
arr.forEach(val => {
if (Array.isArray(val)) {
result.push(...val);
} else {
result.push(val);
}
});
return result;
}
// used like:
const promises = flatten(userData.checks.map(check => {
// ... etc
}));
const checkData = await Promise.all(promises);

I am trying to resolve the data fetched from a firebase query but the data is not getting resolved

I am trying to fetch the data from firebase through node but the data is not being resolved. I cant get the data i want. Basically i want the timestamp from the firebase data but cannot get it.
I have already tried to resolve it using promise and foreach function but no good.
router.post("/date", (req, res) => {
const date = {
sdate: req.body.sdate,
edate: req.body.edate
};
let s = new Date(date.sdate).getTime();
let e = new Date(date.edate).getTime();
const coll = db
.collection("calls")
.where("time", ">=", `${s}`)
.where("time", "<=", `${e}`)
.get()
.then(doc => {
doc.forEach(data => {
console.log(data.id, data.data());
});
})
.catch(err => {
console.log(err);
});
console.log(coll);
I expect to resolve the data into simple object or array so i can render it to the template engine to show the data.
It looks as though you are trying to get multiple documents from a collection. Your code example is confusing due to variable names not matching their meaning.
It is also unclear in your question which piece of your code sample is failing to produce the expected outcome but I suspect it is the console.log(coll). This is because you are returning nothing inside your .then() and you are not waiting for the promise to be resolved before logging.
router.post("/date", (req, res) => {
const date = {
sdate: req.body.sdate,
edate: req.body.edate
};
let s = new Date(date.sdate).getTime();
let e = new Date(date.edate).getTime();
const coll = []
db
.collection("calls")
.where("time", ">=", `${s}`)
.where("time", "<=", `${e}`)
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
coll.push({id: doc.id, data: doc.data()});
});
console.log(coll);
})
.catch(err => {
console.log(err);
});
})

Categories

Resources