3

I am writing a Firebase Cloud Function in node.JS that reads user data from Firebase Firestore. I am unable to push the token values into the token array and return all tokens at the end of the function. My code is as follows:

function getTokens(subscribers) {
    return new Promise(async function (resolve, reject) {
        const tokenArray = [];
        subscribers.forEach(async (subscriber) => {
            await firestore
                .collection('users')
                .doc(subscriber)
                .get()
                .then((user) => {
                    console.log("Getting user data for user: ", user);
                    const tokens = user.data().tokens;
                    if (tokens) {
                        tokens.forEach((token) => {
                            console.log("Adding token to token array"); // data is available here but does not push into tokenArray
                            tokenArray.push(token);
                        });
                    }
                }).catch((error) => { console.error(error); reject(error); });
        });
        console.log("Token Array -- Final: ", tokenArray);
        resolve(tokenArray);
    });
};

2 Answers 2

3

The OP code can be corrected and made more concise as follows:

async function getTokens(subscribers) {
  const getUser = subscriber => firestore.collection('users').doc(subscriber).get();
  const promises = subscribers.map(getUser);
  const users = await Promise.all(promises);
  return users.map(user => user.data().tokens).flat()
}

Some notes:

  • decorate the function as async, since it includes an await
  • don't create any extra promise with Promise.new(), Firestore's get() returns a promise
  • collect get promises in an array with map, and run them with Promise.all()
  • mapping the resulting users' data.tokens produces an array of arrays. Flatten it and you're done.
  • a catch that only throws, is just like having no catch
Sign up to request clarification or add additional context in comments.

1 Comment

I like the .map() with .flat(). Nice!
2

You cannot use async-await in a forEach loop. Try mapping an array of promises and then using Promise.all() as shown below:

function getTokens(subscribers) {
  return new Promise(async function (resolve, reject) {
    
    const subscribersDocs = await Promise.all(
      subscribers.map((subscriber) => {
        return firestore.collection("users").doc(subscriber).get();
      })
    );


    const tokenArray = subscribersDocs.reduce((acc, curr) => {
      const tokens = curr.data().tokens;
      if (tokens) {
        acc = acc.concat(tokens);
      }
      return acc;
    }, []);

    console.log("Token Array -- Final: ", tokenArray);
    resolve(tokenArray);
  });
}

Also checkout: Using async/await with a forEach loop

2 Comments

A couple performance points: if your subscribers at the start is not an totally arbitrary list, it would be better to use a query to select them. Same cost in reads, but only one round trip. Similarly, rather than looping over tokens and using push(), you should be able to use .concat()
Thanks @LeadDreamer. I should have mentioned the in operator though it's useful only if the array has upto 10 elements.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.