My app contains posts with nested comments in Firebase Firestore structured such that each post/comment with docID
has a sub collection postComments
. Thus, a given post/comment can have an infinite number of nested comments.
comments
- docID
postComments
- docID
- docID
- docID
- docID
postComments
- docID
- docID
I am currently writing a Firebase cloud function to recursively query all documents and sub collection documents of a given docID
and return all of those documents in an array. My plan was to define the getChildComments
async function which takes in a docID
and returns all of the documents in that document's postComments
sub collection. I would then recursively call getChildComments
until I have built an array with all of the nested comments in a thread.
exports.loadWholeCommentThread = functions.https.onCall(async (data, context) => {
let comments = await getChildComments(data.rootID);
return comments;
});
async function getChildComments(docID) {
try {
const db = admin.firestore();
const commentsRef = db.collection('comments').doc(docID).collection('postComments');
var comments = [];
const commentsQuerySnap = await commentsRef.get();
commentsQuerySnap.forEach((comment) => {
let commentData = comment.data();
comments.push(commentData);
if (commentData.commentCount > 0) {
let childComments = await getChildComments(commentData.commentID);
comments.concat(childComments);
}
});
return comments;
} catch (error) {
functions.logger.log(error);
throw new functions.https.HttpsError('unknown', 'ERROR0', { message: error.message } )
}
}
Unfortunately, when I try to deploy my code, I get the error Parsing error. Unexpected token getChildComments
on the line where I recursively call getChildComments
inside of getChildComments
. Removing the await from this line fixes the build issue but then the recursive call doesn't finish.
How should I fix my issue? Or is there a better way to query all nested documents?
CodePudding user response:
This is because you have used await
outside of an async
function (note that it is inside an arrow function!).
const comments = [];
const commentsQuerySnap = await commentsRef.get();
commentsQuerySnap.forEach((comment) => {
let commentData = comment.data();
comments.push(commentData);
if (commentData.commentCount > 0) {
let childComments = await getChildComments(commentData.commentID); // the keyword "await" here is invalid
comments = comments.concat(childComments);
}
});
But you can't just add async
to this arrow function, because then your code won't properly wait for the comments
array to be filled.
To properly fix this, you need to use .map()
on the commentsQuerySnap.docs
array in addition to using Promise.all
to wait for each comment (and its children) to be retrieved.
const comments = [];
const commentsQuerySnap = await commentsRef.get();
await Promise.all(
commentsQuerySnap.docs.map(
async (comment) => {
let commentData = comment.data();
comments.push(commentData);
if (commentData.commentCount > 0) {
let childComments = await getChildComments(commentData.commentID);
comments = comments.concat(childComments);
}
})
)
);
While that above block works, the comments array may be out of order to what you were expecting. If you must maintain order of the comments fetched so they are in the same order as the query, you should return the built comments array for each document and then flatten them after they all have been retrieved.
// removed const comments = []; here
const commentsQuerySnap = await commentsRef.get();
const arrayOfCommentThreads = await Promise.all(
commentsQuerySnap.docs.map(
async (comment) => {
let commentData = comment.data();
const commentThread = [commentData];
if (commentData.commentCount > 0) {
let childComments = await getChildComments(commentData.commentID);
commentThread = commentThread.concat(childComments);
}
return commentThread;
})
)
);
const comments = arrayOfCommentThreads.flat();
Personally, I prefer the spread operator to using .concat
like so:
const commentsQuerySnap = await commentsRef.get();
const arrayOfCommentThreads = await Promise.all(
commentsQuerySnap.docs.map(
async (comment) => {
const commentData = comment.data();
if (commentData.commentCount === 0) {
return [commentData];
}
const childComments = await getChildComments(commentData.commentID);
return [commentData, ...childComments];
})
)
);
const comments = arrayOfCommentThreads.flat();