0

I'm working on counting subscribers numbers in users/campaigns on Firebase Cloud Function. But the error occurs when the result total numbers runs faster than running total numbers.

export const howManySubscribers = async () => {
  await firestoreRef
    .collection('users')
    .get()
    .then((userQuerySnapshot) => {
      userQuerySnapshot.forEach((userDoc) => {
        if (!userDoc) return
        userDoc.ref
          .collection('campaigns')
          .get()
          .then(async (campaignQuerySnapshot) => {
            await campaignQuerySnapshot.forEach(async (campaignDoc) => {

              const subscribersNumber = await campaignDoc.ref
                .collection('subscribers')
                .get()
                .then(async (subscriberQuerySnapshot) => {

                  return await subscriberQuerySnapshot.size
                })

              totalSubscribersNumber =
                totalSubscribersNumber + subscribersNumber
            // running total numbers
              console.log('running total numbers', totalNewSubscribersNumber)
              })
            // result total numbers
            console.log('result total numbers', totalNewSubscribersNumber)
          })
      })
    })
}
Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
k10a
  • 947
  • 2
  • 11
  • 30
  • What do you mean by "the error occurs"? Please edit the question to include more information about the error and how that's different than what you expect. – Doug Stevenson Sep 12 '20 at 05:35
  • Also, you should note that forEach doesn't return a promise, so it doesn't make sense to `await` it. async/await doesn't work in a forEach lambda function the way you're expecting it to. – Doug Stevenson Sep 12 '20 at 05:36
  • 1
    You should read up on how to use `async await` and what problem it is trying to solve. You are mixing these with `.then` calls a lot, which makes your code pretty hard to understand. I strongly suggest either fully sticking to `async await` or not using it at all. – pascalpuetz Sep 12 '20 at 08:57

2 Answers2

1

The main Problem is that you are not awaiting the promise chains correctly. At some points, you await something that does not return a promise (e.g. forEach). I strongly suggest refactoring your code to take full advantage of async await and not mix it with then too much, as it makes your code easier to understand and see where you need to await something. In your case, it could look something like this:

export const howManySubscribers = async () => {
  // Get the users (note: await does return the unwrapped value from the return Promise)
  const userQuerySnapshot = await firestoreRef.collection('users').get(); 

  // Get all campaigns, use Promise.all to wait for everything to finish. This will return an Array of the resolved campaigns. Also, leverage 'filter' and 'map'.
  const campaignQuerySnapshots = await Promise.all(
    userQuerySnapshot
      .filter(userDoc => !!userDoc)
      .map(userDoc => userDoc.ref.collection('campaigns').get())
  );

  // Again, use Promise.all to wait for all subscribers to return a value and unwrap it with await
  const subscriberQuerySnapshots = await Promise.all(
    campaignQuerySnapshots.map(campaignDoc => campaignDoc.ref.collection('subscribers').get())
  );
  
  // Now you can just add all sizes from your subscribers and have your total
  const totalNewSubscribersNumber = subscriberQuerySnapshots.map(snap => snap.size).reduce((acc, size) => acc + size, 0);


  // result total numbers
  console.log('result total numbers', totalNewSubscribersNumber)
};
pascalpuetz
  • 5,238
  • 1
  • 13
  • 26
0

forEach doesn't work with await at all. What you can use is

for (item of campaignQuerySnapshot) {
  //do something here with item
}

This works with async and await. You can read more about it in this question

ThienLD
  • 741
  • 4
  • 14