1

Like the title says, I am trying to take a list of IDs from an array, match each ID to the ID in the firestore, and return an array of JSONs of the associated data to front-end site. The code below is returning an empty array.

const functions = require("firebase-functions");
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);


exports.getPlayerDataFromTokens = functions.https.onCall(async (data, context) => {
  return new Promise((resolve, reject) => {
    var db = admin.firestore();
    const tokens = data.tokens;
    let playerArray = [];
    tokens.forEach((token) => {
      const tokensToTokenData = db.collection("Football_Player_Data").where("Token", "==", token);
      tokensToTokenData.get().then((querySnapshot) => {
        querySnapshot.forEach((doc) => {
            // doc.data() is never undefined for query doc snapshots
            playerDataToken = doc.data()["Token"];
            playerDataJersey = doc.data()["Jersey_Number"];
            playerDataMultiplier = doc.data()["Multiplier"];
            playerDataPlayerID = doc.data()["PlayerID"];
            playerDataPosition = doc.data()["Position"];
            playerDataTeam = doc.data()["Team"];
            playerData = {
              "Token": playerDataToken,
              "PlayerID": playerDataPlayerID,
              "Jersey_Number": playerDataJersey,
              "Position": playerDataPosition,
              "Team": playerDataTeam,
              "Multiplier": playerDataMultiplier
            };
            playerArray.push(playerData);
            
        });

      })
      .catch((error) => {
        playerArray = error;
        console.log("Error getting documents: ", error)
        reject(playerArray);
      });
    })
    resolve(JSON.stringify(playerArray));
  });
})

Zzz
  • 13
  • 3
  • 1
    The best way to debug these is to try to `console.log` each piece. I'd start with `console.log({ tokensToTokenData })` to make sure that populates correctly. From there I'd try `console.log({ querySnapshot })`, `console.log({ playerArray })` and so on until you find which piece of code isn't populating. It's possible this function works just fine and you need to call `getPlayerDataFromTokens().then()` – Joseph Cho Oct 23 '21 at 03:00

1 Answers1

0

Based on your code, you are building this based on a very outdated tutorial.

In Firebase Functions V1.0.0 (April 2018), functions.config().firebase was removed. It now resolves as undefined so that the modern admin.initializeApp() (with no arguments) functions properly. When invoked this way, the Admin SDK initializes based on the presence of the appropriate environment variables.

Next, you are using the Explicit Promise Construction Antipattern. In short, do not wrap your code inside new Promise((resolve, reject) => {}).

As this functions code is likely to be deployed to Google Cloud Functions, which runs on Node 10/12/14 you can use modern ES2017 syntax like async/await, deconstruction patterns, import, let, const and Promise.all.

So this means the top of your script consists of:

import * as admin from "firebase-admin";
import * as functions from "firebase-functions";

admin.initializeApp();

As you need to fetch a list of players for each token given, you can break this logic out into it's own function:

/**
 * For the given token, return an array of players linked with
 * that token.
 */
async function getFootballPlayersWithToken(db, token) {
  const playersWithTokenQuerySnapshot = await db
    .collection("Football_Player_Data")
    .where("Token", "==", token)
    .get();

  if (playersWithTokenQuerySnapshot.empty) {
    console.log("Given token did not match any players: ", token);
    return []; // or throw error
  }
  
  const playerArray = await Promise.all(
    playersWithTokenQuerySnapshot.docs
      .map((playerDoc) => {
        const { Token, Jersey_Number, Multiplier, PlayerID, Position, Team } = playerDoc.data();
        return { Token, Jersey_Number, Multiplier, PlayerID, Position, Team };
      });
  );
  
  return playerArray;
}

In the above code block, it's important to note that playersWithTokenQuerySnapshot is a QuerySnapshot object, not an array. While forEach is available on the QuerySnapshot object, to use normal Array methods like .map and .reduce, you need to use the docs property instead.

This makes your Cloud Function:

exports.getPlayerDataFromTokens = functions.https.onCall(async (data, context) => {
  const { tokens } = data;
  
  try {
    const db = admin.firestore();
    
    const playersGroupedByTokenArray = await Promise.all(
      tokens.map(token => getFootballPlayersWithToken(db, token))
    );
    
    // playersGroupedByTokenArray is an array of arrays
    // e.g. [[token1Player1, token1Player2], [token2Player1], [], ...]
    // flatten this array
    
    // Node 12+:
    // const playerArray = playersGroupedByTokenArray.flat()
    // Node 10+:
    const playerArray = [];
    playersGroupedByTokenArray.forEach(
      groupOfPlayers => playerArray.push(...groupOfPlayers)
    );
    
    return playerArray;
  } catch (error) {
    console.error("Error finding players for one or more of these tokens: ", tokens.join(", "))
    throw new functions.https.HttpsError(
      "unknown",
      "Could not get players for one or more of the tokens provided",
      error.code || error.message
    );
  }
);

As shown in the above code block, for errors to be handled properly, you must wrap them in a HttpsError as documented here.

If a token would return only one player, you can tweak the above get players function to just the following and then edit your Cloud Function as needed:

/**
 * For the given token, return the matching player.
 */
async function getFootballPlayerByToken(db, token) {
  const playersWithTokenQuerySnapshot = await db
    .collection("Football_Player_Data")
    .where("Token", "==", token)
    .get();

  if (playersWithTokenQuerySnapshot.empty) {
    console.log("Given token did not match any players: ", token);
    return null; // or throw error
  }
  
  const playerDoc = playersWithTokenQuerySnapshot.docs[0];

  const { Token, Jersey_Number, Multiplier, PlayerID, Position, Team } = playerDoc.data();
  return { Token, Jersey_Number, Multiplier, PlayerID, Position, Team };
}
samthecodingman
  • 23,122
  • 4
  • 30
  • 54