How to fetch documents coming from different collections in Firestore inside a Redux Saga - google-cloud-firestore

I have the following saga:
export function* fetchAnalyticsData() {
try {
const data = [];
const collectionReference = firestore.collection("collection1")
const UIDSreference = yield collectionReference.get();
// getUID is a function that returns an array of UIDS of documents of collection1
const UIDS = yield call(getUID, collectionReference);
const populate = yield all(
UIDS.map((uid) => {
firestore
.collection("collection1")
.doc(uid)
.collection("collection2")
.get()
.then((response) => {
if (response.docs.length) {
response.docs.forEach((doc) => data.push(doc.data()));
console.log(data);
}
});
})
);
console.log(data);
yield put(fetchAnalyticsDataSuccess(data));
} catch (error) {
console.log(`Error in fetchAnalyticsData: ${error}`);
yield put(fetchAnalyticsDataFailure(error.message));
}
}
The inner console.log(data) prints the data array correctly filled. However, the outer console.log(data) prints an empty array. I know it's because these things are promises, but then how can I solve this issue?

Related

Firebase cloud functions not waiting for forEach to complete before jumping to the next then

Been trying to copy subcollections of a collection into another collection. The code below is aimed at that, but jumps from the first then and logs out "Done" without logging out anything before.
So the question is what is not correct here?
exports = module.exports = functions.https.onRequest(async (req, res) => {
let db = admin.firestore();
try {
await db.collection("users").get().then((query) => {
return query.forEach(async (doc) => {
console.log("Here"); //This doesn't print
const polCollection = await db.collection("users").doc(doc.id).collection("xyz").get();
if (polCollection.docs.length > 0) { //This checks if any subcollections
for (const x of polCollection.docs) { //This copies them into a doc in the copy collection
db.collection("CopyUsers")
.doc(doc.id)
.set({ x : x.data() }, { merge: true });
}
}
});
})
.then(() => {
console.log("Done"); //This is the only thing that prints in the console
res.end();
})
.catch((e) => {
console.log("e", e);
res.end();
});
} catch (error) {
console.log("error", error);
res.end();
}
});
After the suggestion below, it now looks as follows:
exports = module.exports = functions.runWith(runtimeOpts).https.onRequest(async (req, res) => {
const promises = [];
let count = 0;
let size = 0;
return await admin
.firestore()
.collection("testUsers")
.get()
.then((query) => {
console.log("query length:", query.size); //prints x of users
size = query.size;
query.forEach(async (doc) => {
const promise = async () => {
console.log("Here", doc.id); //This doesn't print
await admin
.firestore()
.collection("testUsers")
.doc(doc.id)
.collection("xyz")
.get()
.then(async (polCollection) => {
if (polCollection.docs.length > 0) {
for (const x of polCollection.docs) {
return await admin
.firestore()
.collection("testBackUpUsers")
.doc(doc.id)
.set(
{ xyz: x.data() },
{ merge: true }
);
}
} else {
return;
}
})
.catch((e) => console.log("error from then after get xyz", e));
};
count++;
return promises.push(promise);
});
return promises;
})
.then(async (promises) => {
if (size <= count) {
console.log("running return Promise.all(promises)", promises.length); //prints number of promises = users
return Promise.all(promises);
}
})
.catch((e) => console.log("err from the last catch", e));
});
Any thoughts?
Unfortunately the forEach iterator does not support async/await. Even if you write an await inside it will just go trough it without waiting on the execution.
I would recommend to use Promise.all. That would also execute the code in parallel and would finish faster.
If you would only change data you could also use a batch change but in your example you first get the data and then change it.
Here is an example how you could write your code:
exports = module.exports = functions.https.onRequest(async (req, res) => {
let db = admin.firestore();
const promises = [];
try {
const query = await db.collection("users").get();
query.forEach((doc) => {
console.log("doc", doc);
const promise = async () => {
console.log("Here", doc.id); //This doesn't print
const polCollection = await db
.collection("users")
.doc(doc.id)
.collection("xyz")
.get();
if (polCollection.docs.length > 0) {
//This checks if any subcollections
for (const x of polCollection.docs) {
//This copies them into a doc in the copy collection
await db
.collection("CopyUsers")
.doc(doc.id)
.set({ x: x.data() }, { merge: true });
}
}
};
promises.push(promise);
});
console.log("promises", promises);
await Promise.all(promises);
console.log("Done");
res.end();
} catch (error) {
console.log("error", error);
res.end();
}
});

Mongoose find return an empty array but return values when i set a variable to the model

why is the first query not working
I am using mongoose
i want to e able to check for errors during query that why I want to use the first instance
import Work from './WorkModel.js'
const response = await Work.findOne({ user: userId}).exec((err, result) => {
if (err) {
throw new Error('Try again later');
} else {
console.log(odemruId);
return result;
}
});
return response
//this return no value
const response= await Work.findOne({user: userId})
return response
//this actual works
```

Mongoose findOne variables

I have a function which passes a key val and then gets added as arguments for a .findOne() mongoose function.
getByKey = async (key, val) => {
console.log(key, val);
const user = await UserSchema.findOne({
key: val
});
console.log(user);
return user;
};
The problem is, I think mongoose is actually searching the collection for the word key instead of the what it stands for ie: "username" or "age"
It is looking for 'key' as opposed to the key your passing. You can accomplish what you're trying to do by doing something like this.
var query = {}
query[key] = value;
And then pass that query to your findOne function. See below.
getByKey = async (key, val) => {
var query = {}
query[key] = value;
const user = await UserSchema.findOne(query);
console.log(user);
return user;
};
You also might want to consider adding a callback function to in your findOne and try to log the data if it was found.
const user = await UserSchema.findOne(query, function(err, data){
if (err){
console.log(err)
} else {
console.log(data)
}
});
You can use Computed property names to handle this. Example:
const user = await UserSchema.findOne({
[key]: val
});

Why resolving an async promise with a .map() function doesn't work for GET with parameters?

I am not sure how to express my question correctly.
Basically resolving an async promise with a .map() function works for simple get functions while it doesn't work for get functions with parameter.
Basically, in this case, router.get('/' ... the following works:
import axios from 'axios'
const url = 'http://localhost:3000/api/library/'
class libraryService {
// Get stories
static getStories () {
return new Promise(async (resolve, reject) => {
try {
const res = await axios.get(url)
const data = res.data
resolve(
data.map(story => ({
...story
}))
)
} catch (err) {
reject(err)
}
})
}
export default libraryService
While in this case, router.get('/:story_name' ..., this variation doesn't work:
class readService {
// Get story to read
static getStoryToRead (storyName) {
return new Promise(async (resolve, reject) => {
try {
const res = await axios.get(url + storyName)
const data = res.data
resolve(
data.map(selectedStory => ({
...selectedStory
}))
...
In here I get an error: 'data.map is not a function'.
Changing to data.products.map() will return an error 'Cannot read property 'map' of undefined'.
However resolving data without .map() function will work on all cases:
try {
const res = await axios.get(...)
const data = res.data
resolve(
data
)
...
Why this is happening and is it correct to just use resolve(data)?
You seem to be asking for a single story in the case that doesn't work. So instead of an array of stories, presuambly you're getting just the one story that you asked for. There's no reason to try to use map.
Minimal changes (but keep reading):
// Minimal changes, but keep reading...
static getStoryToRead (storyName) {
return new Promise(async (resolve, reject) => {
try {
const res = await axios.get(url + storyName);
resolve(res.data);
} catch (err) {
reject(err);
}
});
}
But, both of those functions demonstrate the Promise creation antipattern. You already have a promise, work with it. In this case, you'd probably do that by making the functions async:
static async getStories () {
const {data} = await axios.get(url);
return data.map(story => ({ // Why copy the story objects?
...story
}));
}
static async getStoryToRead (storyName) {
const {data} = await axios.get(url + storyName));
return data;
}
Or with non-async functions:
static getStories () {
return axios.get(url)
.then(({data}) => data.map(story => ({...story}))); // Why copy the story objects?
}
static getStoryToRead (storyName) {
return axios.get(url + storyName))
.then(({data}) => data);
}

How to make a request to receive random documents based on id? limit(10)

Firestore(web)
Help me write a request for random documents based on ID
Can I do this without getting the entire collection?
const questions = [];
const docRef = db.collection('ru').doc(category).collection('questions');
try {
const doc = await docRef.get();
doc.forEach((item) => {
questions.push(item.data());
});
} catch (e) {
throw e;
}