1

I have a cloud function that processes and stores data sent to it. Most of the data comes from the request except for some ID values I store in the main collection I am working with for reference. My problem is the data I get from the other cloud firestore collection is not persisted in the collection I am writing to. My code looks like this:

await emails.forEach(async (email: string) => {
    try {
        // This should only ever return one result
        const student = await usersRef.where('userEmail', '==', email).get();
        if (student.empty) {
            console.log(`No matching documents for email: ${email}`);
        } else {
            student.forEach((s) => {
                const studentData = s.data();
                console.log('found student: ', studentData);
                // StudentIds stores reference to student objects
                studentIds.push(studentData.playerUniqueID); 
            });
        }
    } catch (err) {
        console.log('Error finding students: ', err);
        throw new functions.https.HttpsError('internal', 'Error finding students');
    }
});

const sessionId = uuidv4();
const newSession: newSessionWriteRequest = {
    emails, // emails is also an array of strings and is persisted properly
    owner,
    // StudentIds is not empty at this point and is populated correctly. StudentIds is an array of strings
    studentIds,
    ongoing: true,
    sessionName: data.sessionName,
    startTime: data.sessionStartDate,
    sessionId
};

try {
    // All values except studentIds are persisted to the sessionsRef except studentIds, which is a blank array
    await sessionsRef.doc(sessionId).set(newSession);
    console.log('new session: ', newSession);
    return newSession;
} catch (err) {
    console.log('Error creating new session: ', err);
    throw new functions.https.HttpsError('internal', 'Error creating new session');
}

StudentIds is just an array of strings. emails is also an array of string but is stored correctly. The only difference between the two is that emails comes from the initial request to the function whereas studentIds comes from firestore. My question is why is studentIds not being persisted correctly? Is there some kind of interaction between firebase collections I am not aware of?

Parzh from Ukraine
  • 7,999
  • 3
  • 34
  • 65
  • How are you sure that "StudentIds is not empty at this point" in your comment? Where is your log line that proves it, at that specific point in the code? The log lines inside the forEach aren't enough proof, given the asynchronous nature of the code at that point. – Doug Stevenson Aug 30 '20 at 05:34
  • When I log out the `newSession` object I can clearly see that the `studentIds` array has been populated. Also why would the console.logs in the forEach loop (assuming you're talking about the one looping over `emails`) be unreliable? I have an `await` in front of the function call. – aprdrivesmecrazy Aug 30 '20 at 06:27
  • forEach doesn't return a promise, so you can't really await it. That await effectively does nothing, and the code after the forEach will execute before the async work in the loop is complete. Please edit the question to show how you are logging, and the contents of the log output. – Doug Stevenson Aug 30 '20 at 06:48
  • Indeed what Doug mentioned is correct, the reason why it's not persisting is that the `StudentIds` is likely not properly populated due to the promises not being resolved by the time you get to the `newSession` part of your code, try using `for ... of` intead of `foreach`, as mentioned on this [community answer](https://stackoverflow.com/a/37576787/12857703) and let me know if it works. – Ralemos Aug 31 '20 at 13:38
  • Now that I look at it again I think you guys are right! Thanks for the help! – aprdrivesmecrazy Aug 31 '20 at 15:26

1 Answers1

1

The issue lied with this block of code:

await emails.forEach(async (email: string) => {
    try {
        // This should only ever return one result
        const student = await usersRef.where('userEmail', '==', email).get();
        if (student.empty) {
            console.log(`No matching documents for email: ${email}`);
        } else {
            student.forEach((s) => {
                const studentData = s.data();
                console.log('found student: ', studentData);
                // StudentIds stores reference to student objects
                studentIds.push(studentData.playerUniqueID); 
            });
        }
    } catch (err) {
        console.log('Error finding students: ', err);
        throw new functions.https.HttpsError('internal', 'Error finding students');
    }
});

As pointed out in the comments, my await in front of the forEach loop was doing nothing. Changing it to this fixed the issue:

const studentIds: string[] = await getStudentPlayerUniqueIds(emails);
...
const getStudentPlayerUniqueIds = async(emails: string[]): Promise<string[]> => {
  const studentIds: string[] = []
  try {
    for (const email of emails) {
      const student = await usersRef.where('userEmail', '==', email).get();
      if (student.empty) {
        console.log(`No matching documents for email: ${email}`);
      } else {
        student.forEach((s) => {
          const studentData = s.data();
          console.log('found student: ', studentData);
          studentIds.push(studentData.playerUniqueID);
        });
      }
    }
    return studentIds;
  } catch (err) {
    console.log('Error finding students: ', err);
    throw new functions.https.HttpsError('internal', 'Error finding students');
  }
}