Search code examples
node.jsfirebasegoogle-cloud-platformgoogle-cloud-firestoregoogle-cloud-functions

Random document from Firestore code occasionally breaks in a Firebase function


I have the following code that gets a random Firestore document given some parameters

const { onSchedule } = require("firebase-functions/v2/scheduler");
const { logger } = require("firebase-functions");

const admin = require("firebase-admin");
admin.initializeApp();
var db = admin.firestore();

function getRandomFromCollection(collection, level, enabled) {
    return new Promise(function (resolve, reject) {
        logger.info("Starting random search")
        let key = collection.doc().id;
        logger.info("random key done");
        collection.where("enabled", "==", enabled).where("level", "==", level).where("__name__", '>=', key).limit(1).get()
            .then(snapshot => {
                if (snapshot.size > 0) {
                    snapshot.forEach(doc => {
                        logger.info("done");
                        resolve(doc);
                    });
                }
                else {
                    collection.where("enabled", "==", enabled).where("level", "==", level).where("__name__", '<', key).limit(1).get()
                        .then(snapshot => {
                            snapshot.forEach(doc => {
                                logger.info("done");
                                resolve(doc);
                            });
                        })
                        .catch(err => {
                            logger.info("error");
                            reject(err);
                        });
                }
            })
            .catch(err => {
                logger.info("error");
                reject(err);
            });
    });
}

exports.newQuestions = onSchedule({
    schedule: "every day 9:55",
    maxInstances: 1,
    timeZone: "UTC"
},

    async (event) => {
        let collection = db.collection("questions");
        getRandomFromCollection(collection, 1, false).then(doc => {
            logger.info("Got question");


        }).catch(err => {
            logger.error(`ERROR! Something when wrong when generating question! Error: ${err}`)
        })

    });

I have ran this code many times in the functions emulator with no issue but when I deploy it to firebase to be ran once a day it seems to fail around every 1 in 5 calls.

When the error occurs the function logs shows the "Starting random search" and "Random key done" but then returns nothing else and the promise isn't resolved or rejected.


Solution

  • You don't correctly manage the life cycle of your Cloud Function, see the documentation or other SO answers.

    Since you use the async keyword I've re-written your CF as below, using async/await. (Do not hesitate to add comments to this answer if necessary, in order to possibly fine tune together this proposed solution).

    async function getRandomFromCollection(collection, level, enabled) {
    
        logger.info("Starting random search");
        let key = collection.doc().id;
        logger.info("random key done");
    
        const snapshot = await collection
          .where("enabled", "==", enabled)
          .where("level", "==", level)
          .where("__name__", ">=", key)
          .limit(1)
          .get();
    
          if (snapshot.size > 0) {
            // No need to use snapshot.forEach since 
            // there is only 1 doc in the QuerySnapshot
            // if snapshot.size > 0
            // snapshot.docs[0] is the unique DocumentSnapshot
            return snapshot.docs[0];
            
          } else {
            const snapshot = await collection
            .where("enabled", "==", enabled)
            .where("level", "==", level)
            .where("__name__", "<", key)
            .limit(1)
            .get();
    
            if (snapshot.size > 0) {
              return snapshot.docs[0];
            }
          }
    }
    
    exports.newQuestions = onSchedule(
      {
        schedule: "every day 9:55",
        maxInstances: 1,
        timeZone: "UTC",
      },
    
      async (event) => {
        try {
          let collection = db.collection("questions");
          await getRandomFromCollection(collection, 1, false);
          logger.info("Got question");
          return true;
        } catch (error) {
          logger.error(
            `ERROR! Something when wrong when generating question! Error: ${err}`
          );
          return true;
        }
      }
    );
    

    PS: You may be interested by this SO answer on "How to get random documents in a collection".