1

I am trying to iterate over an array of comments and need to grab the commenter's uid for each comment. I am a beginner to JavaScript and need a little bit of help with the following use case:

I need to grab the uid for each comment and then run a .getUser() method which will return the user's email address that is associated with the user's uid. Since .getUser() returns a promise (method reference link), I need to await somewhere in this loop. How to do so? Is this even a good approach?

(Note: My end goal is to eventually attach the email addresses to a to property in a msg object where I will then send out email notifications.)

Example data for comments:

[
  {
    id: 1,
    uid: 'RGaBbiui'
  },
  {
    id: 2,
    uid: 'ladfasdflal'
  },
  {
    id: 3,
    uid: 'RGaBbiui'
  },
  {
    id: 4,
    uid: 'RGaBbiui'
  },
  {
    id: 5,
    uid: 'ladfasdflal'
  },
  {
    id: 6,
    uid: 'ladfasdflal'
  }
]

Cloud function example:

export const sendCommentNotification = functions.firestore
  .document('users/{uid}/posts/{postId}/comments/{commentId}')
  .onCreate(async (snapshot, context) => {
    try {

  const commentsQuery = await admin
    .firestore()
    .collection(
      `users/${context.params.uid}/posts/${context.params.postId}/comments`
    )
    .get()

  const commentsArr = []

  commentsQuery.forEach((documentSnapshot) =>
    commentsArr.push(documentSnapshot.data())
  )

  const commentsArrUids = new Set(commentsArr.map((c) => c.uid))
  console.log(commentsArrUids)

  const emailAddresses = []
  commentsArrUids.forEach((uid) =>
    emailAddresses.push(admin.auth().getUser(uid))  // how to use await here?
  )

      ...

      const msg = {
        to: //TO DO..put email addresses here..
        ...
Dharmaraj
  • 47,845
  • 8
  • 52
  • 84
redshift
  • 4,815
  • 13
  • 75
  • 138
  • Don't use async/await in forEach - there's a question on here which explains why - here tis - https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop – Bravo Jul 09 '21 at 11:03
  • thanks, but thats where i need help with...how to await it so i can grab the email addresses. – redshift Jul 09 '21 at 11:05
  • 1
    so, none of the code in that question helps? Interesting – Bravo Jul 09 '21 at 11:54
  • appreciate it but it makes more sense if i see code that uses code i had written...then once i understand it, ill look at the question you posted. I did check out the question but still had some questions....check out answers below. Incredible responses. – redshift Jul 09 '21 at 12:05

3 Answers3

4

You cannot use await in a forEach loop. You could use await in a for-loop but they won't run simultaneously as in Promise.all().

You can just await all the promises at once using Promise.all():

Returned values will be in order of the Promises passed, regardless of completion order.

const emailAddresses = []
commentsArrUids.forEach((uid) => {
  emailAddresses.push(admin.auth().getUser(uid))
})
const data = await Promise.all(emailAddresses)

Data will be an array of UserRecord.

Then you can use the .map() method to get an array of all the emails.

const emails = data.map((user) => user.email)

The code can be written like this to make it easier:

const commentsDocs = await admin.firestore().collection(`users/${context.params.uid}/posts/${context.params.postId}/comments`).get()

const userIds = commentsDocs.docs.map(comment => comment.userId)
const usersReq = userIds.map(u => admin.auth().getUser(u.uid))
const emails = (await Promise.all(usersReq))).map((user) => user.email)
Dharmaraj
  • 47,845
  • 8
  • 52
  • 84
2

Use for loop instead.

for (let i = 0; i < commentsArrUids.length; i++) {
  let user = await new Promise((resolve, reject) => {
    admin.auth().getUser(commentsArrUids[i]).then((user) => {
      resolve(user);
    });
  };
  emailAddresses.push(user);
}
Kostas
  • 1,903
  • 1
  • 16
  • 22
2

I will replace forEach with for of and the promises is in series. Also, I rewrite some of your codes as they are redundant.

export const sendCommentNotification = functions.firestore
  .document("users/{uid}/posts/{postId}/comments/{commentId}")
  .onCreate(async (snapshot, context) => {
    try {
      const comments = await admin
        .firestore()
        .collection(
          `users/${context.params.uid}/posts/${context.params.postId}/comments`
        )
        .get();

      const uids = new Set();

      for (const comment of comments) {
        uids.add(comment.data().uid);
      }

      const emailAddresses = [];

      for (const uid of uids) {
        const res = await admin.auth().getUser(uid);
        emailAddresses.push(res);
      }
    } catch (err) {
      console.log(err);
    }
  });
ikhvjs
  • 5,316
  • 2
  • 13
  • 36