How to use Promise.all with multiple Firestore queries - javascript

I know there are similar questions to this on stack overflow but thus far none have been able to help me get my code working.
I have a function that takes an id, and makes a call to firebase firestore to get all the documents in a "feedItems" collection. Each document contains two fields, a timestamp and a post ID. The function returns an array with each post object. This part of the code (getFeedItems below) works as expected.
The problem occurs in the next step. Once I have the array of post ID's, I then loop over the array and make a firestore query for each one, to get the actual post information. I know these queries are asynchronous, so I use Promise.all to wait for each promise to resolve before using the final array of post information.
However, I continue to receive "undefined" as a result of these looped queries. Why?
const useUpdateFeed = (uid) => {
const [feed, setFeed] = useState([]);
useEffect(() => {
// getFeedItems returns an array of postIDs, and works as expected
async function getFeedItems(uid) {
const docRef = firestore
.collection("feeds")
.doc(uid)
.collection("feedItems");
const doc = await docRef.get();
const feedItems = [];
doc.forEach((item) => {
feedItems.push({
...item.data(),
id: item.id,
});
});
return feedItems;
}
// getPosts is meant to take the array of post IDs, and return an array of the post objects
async function getPosts(items) {
console.log(items)
const promises = [];
items.forEach((item) => {
const promise = firestore.collection("posts").doc(item.id).get();
promises.push(promise);
});
const posts = [];
await Promise.all(promises).then((results) => {
results.forEach((result) => {
const post = result.data();
console.log(post); // this continues to log as "undefined". Why?
posts.push(post);
});
});
return posts;
}
(async () => {
if (uid) {
const feedItems = await getFeedItems(uid);
const posts = await getPosts(feedItems);
setFeed(posts);
}
})();
}, []);
return feed; // The final result is an array with a single "undefined" element
};
There are few things I have already verified on my own:
My firestore queries work as expected when done one at a time (so there are not any bugs with the query structures themselves).
This is a custom hook for React. I don't think my use of useState/useEffect is having any issue here, and I have tested the implementation of this hook with mock data.
EDIT: A console.log() of items was requested and has been added to the code snippet. I can confirm that the firestore documents that I am trying to access do exist, and have been successfully retrieved when called in individual queries (not in a loop).
Also, for simplicity the collection on Firestore currently only includes one post (with an ID of "ANkRFz2L7WQzA3ehcpDz", which can be seen in the console log output below.
EDIT TWO: To make the output clearer I have pasted it as an image below.

Turns out, this was human error. Looking at the console log output I realised there is a space in front of the document ID. Removing that on the backend made my code work.

Related

Unable to perform a Firebase query on a timestamp and receiving an empty body in response

Can someone explain to me why I am not able to perform a simple Firebase query checking for a specific timestamp in a subcollection?
The code below works if I try to retrieve the whole document, but if I add the where query it just returns a 200 response with an empty body.
I have also tried to replace db.collection with db.collectionGroup and in this case I get a 500 response with the following message Collection IDs must not contain '/'.
Here you can see how I have structured my data and my code:
try {
const reference = db.collection(`/data/10546781/history`).where("timestamp", "==", 1659559179735)
const document = await reference.get()
res.status(200).json(document.forEach(doc => {
doc.data()
console.log(doc.data())
}))
} catch(error) {
res.status(500).json(error)
console.log(error)
};
It seems you are looking for map() that creates a new array and not forEach() loop that returns nothing. Try:
const reference = db.collection(`/data/10546781/history`).where("realtimeData.timestamp", "==", 1659559179735)
const snapshot = await reference.get()
const data = snapshot.docs.map((d) => ({
id: d.id,
...d.data()
}))
res.status(200).json(data)
Additionally, you need to use the dot notation if you want to query based on a nested field.
#Dharmaraj Thanks for the help. Your answer was part of the solution. The other part concerned how my data was structured. The timestamp needed to be at the parent level of the subcollection document. So either outside the realTimeData object or the whole object needs to be flattened at the parent level.

In getServerSideProps, how to execute the return statement after all fetching is complete?

I have access to the username of a person and I can access their corresponding data object using one fetch from firebase. This data object has a lot of properties, including one which is called "uploads" which is an array of documentIDs where each id is that of a post uploaded by the user. I want to fetch all these documents, make it an array and return the ENTIRE array back to the page.
I have written this code so far: https://i.stack.imgur.com/OC74t.png
What is happening is that the return on line 54 executes before all elements of postIDs is looped through because of which, an empty array is returned back to the component.
Let me know if further details are required. Please help me out. Thanks in advance!
Try to change your loop to a for...of:
for (const postID of currentUserData.uploads) {
const postReference = doc(db, `posts/${postID}`)
const snapshot = await getDoc(postReference)
const postData = snapshot.data()
postData.time = postData.time.toJSON()
uploadsArray.push(postData)
}
return { props: { uploadsArray }}
What about try to use Promise.all?
var promises = [];
promises.push();
// Wait for all promises to resolve
Promise.all(promises).then(function(res) {
// Do something...
});

Why is Firebase Query [ ] despite async/await?

I'm still struggling to understand how to extract values from a Firestore Query and put them into a global variable. I (now) understand that the asynchronous nature means that code isn't executed in the order that it is written. But, why is user still undefined in the following code block despite the await keyword inside of an async function? I understand (based on this question) that await should be applied only up to get(), otherwise there may not be values to iterate over.
My question is similar to this, but I run into the same issue as the comment and it looks like the question was never marked as answered. It is also similar to this, but pushing all gets to a Promise and then resolving that promise still didn't assign the data to a variable. It just printed an empty array.
I can print the variable, so my question is about assignment, I think. This function is a handler for an Intent in DialogFlow. I strongly prefer to have the data available outside of the call to the db. Adding to agent responses in a call to firestore doesn't always add the text to the agent, so I'd like to avoid that.
async function loginHandler(agent){
username = agent.parameters.username.name;
password = agent.parameters.password;
const user = await db.collection("users")
.where("name","==",username)
.where("password","==",password)
.limit(1)
.get()
.then(querySnapshot =>{
querySnapshot.forEach(docSnapShot =>{
return docSnapShot.data();
//console.log.(docSnapShot.data()); //prints correct contents, so error is in programming logic
agent.add("Response"); // Would prefer to avoid this, but could refactor if I am fundamentally misunderstanding how to access Firebase data
})
})
.catch(error => console.log);
console.log(user);
console.log("bort");
}
Picture of Firestore to demonstrate that the correct data do exist:
You might be able to split the code up. Return the data from the database first, then map over the data to extract the details, and then assign that result to the user variable.
async function loginHandler(agent) {
username = agent.parameters.username.name;
password = agent.parameters.password;
// `await` the promise and assign it to
// `querySnapshot`
const querySnapshot = await db.collection('users')
.where('name', '==', username)
.where('password', '==', password)
.limit(1)
.get()
.catch(error => console.log);
const user = querySnapshot.docs.map(docSnapShot => {
agent.add('Response');
return docSnapShot.data();
});
console.log(user);
}
forEach iterates the results but doesn't return anything, so your return inside there isn't doing what you'd expect (forEach returns void so you're returning the snapshot to a function that is returning void). You can create a local variable to hold the results you iterate and then return that:
const user = await db.collection("users")
.where("name","==",username)
.where("password","==",password)
.limit(1)
.get()
.then(querySnapshot =>{
// Set up an empty array to return later
let data = []
querySnapshot.forEach(docSnapShot =>{
// Add each snapshot's data object to the array
data = [...data, ...docSnapShot.data()]
})
// Return `data` which will populate `user`
return data
})
.catch(error => console.log);
Have you checked what the correct answer is?
Please clarify whether you mean undefined as you say in the title, or empty array as you say in the text.
Your code looks correct to me. I would not expect the output of console.log(user) to be undefined. However, an empty list [ ] would be a perfectly reasonable answer.
Perhaps there is nobody in that collection with that username and password?
Have you tried removing the condition of equality of username and password? That should get you an element of the collection, if it is not entirely empty.

Async/Await variable affectation in javascript for loop

I would like to achieve the following thing in JS but I've got the feeling that I don't have the right approach.
I'm doing a first request giving me a list of objects
For each object I'm doing another request to an API to get matching values
Apply a specific logic to the result
Finally call a function that uses the data provided by the API and modified
My code looks like this :
async function processData(){
const models = await fetch('/api/models').then(response => response.json());
for(let m in models){
for(let i of models[m].items){
if(i.type == 1){
i.values = await fetch('/api/main/'+i.id).then(response => response.json());
}
else{
i.values = await fetch('/api/custom/'+i.id).then(response => response.json());
// + additionnal specific code on values
}
console.log(i.values);//<--- NOT WHAT EXPECTED
}
display(models[m]);
}
}
My problem is that display function is called before i.values is updated.
I think the whole for(let i models[m].items){} loop should be asynchronous and the display function should be called only once all fetch requests for each i item have been resolved.
But I'm struggling to write the code and I'm not sure this is the good way of doing this.

Catching part of a JSON response and saving into a variable?

I have a question I've spent the past few days with my friend google trying to answer. This is the code from a project I'm currently working on and I'm trying to interface with two API's.
What you see here is a call to the first API using the GOT library formatting to receive a JSON response.
var products
//Printify call for products list
(async () => {
try{
const list = await redd('shops/shopId/products.json');
//Catch the Data array and save it into the variable products
var obj = new JSONObject(response);
products = obj.getJSONArray("data");
}
catch(error) {
}
})();
//Print the variable products to the console
console.log(products)
I create a new JSONObject from the response and grab the Data Array from that response and put it in the Variable products which was defined outside this function. Finally I'm trying to print the variable to the console.
Eventually I will need to take that "data" Array and parse for specific items inside it (i.e. title: , description: , images:) and pass it as a value into the next API.
Currently I'm getting a "undefined" response from the console. Not sure what I'm doing wrong, hoping that I can get some help or direction. Anything is appreciated, thank you all in advance!
I was finally able to get it all working. It's actually been a minute since I solved, but wanted to make sure it worked. Here is the final code that ended up working for me:
//Config for a new GOT instance, to use redd as the variable with the attached headers and options
const redd = got.extend({
prefixUrl: 'https://api.printify.com/v1',
responseType: 'json',
headers: {
'Authorization': 'Bearer ' + apiKey
}
});
var productsParsed //Setting up global variable to accept the array from the JSON response body
//Printify call for products list
getProducts = async () => {
try{
const response = await redd('shops/' + shopId + '/products.json');
productsParsed = response.body.data; //Data is the array in the body I need access to, and saving that into the var productsParsed
//Returning the var with the new value
return productsParsed
}
catch(error) {
console.log(error.response);
}
};
getProducts().then(console.log); //Printing the return value from getProducts() which verifies the var contains the needed value
So the solution ended up being fairly simple. I just didn't fully understand the GOT structure. I had to use dot notation to pin down my return statement. After figuring that out I was able to use my global variable that I had set up to accept the value of the JSON response body. Thank you all for your suggestions and assistance. I hope this post is able to help any others in a similar situation to myself.
You've defined an async function, but you haven't awaited its result.
I think adding await in front of the call site would fix that.
Because it's an async function the console.log is called before the async function is finished. Move it inside the async function and it should work.
var products
//Printify call for products list
(async () => {
try{
const list = await redd('shops/shopId/products.json'); // we wait for the response here
//Catch the Data array and save it into the variable products
var obj = new JSONObject(response);
products = obj.getJSONArray("data");
//Print the variable products to the console will work here
console.log(products)
}
catch(error) {
}
})();

Categories

Resources