thebenCA
thebenCA

Reputation: 159

Scheduled Firebase Function - No writing to Firestore

My schedule Cloud Function is running on schedule, but not successfully writing to my Firestore instance. The code successfully writes to the FirestoreDB when I trigger it as an HTTP request locally. But it does not appear to be writing to Firestore once deployed with PubSub scheduling logic added.

The Function logs in GCP are showing a finished status of "ok".

Wondering if I'm doing something works in JS, but GCP or Pubsub doesn't like, even though it's technically valid JS?

Any help or direction is appreciated.

const admin = require("firebase-admin");
const request = require("request");
const functions = require("firebase-functions");

const serviceAccount = require("......");

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
});
const db = admin.firestore();

exports.scheduledPRRfunction = functions.pubsub
    .schedule("every 2 minutes")
    .timeZone("America/New_York")
    .onRun(((context) => {
      const Options = {
        "method": "GET",
        "url": "...",
        "headers": {
          "Cookie": ".....",
        },
      };

      db.collection("myCollectionName").get().then((querySnapshot) => {
        querySnapshot.forEach((doc) => {
          const documentIds = doc.id;
          // Delete Documents
          db.collection("myCollectionName")
              .doc(documentIds).delete().then(() => {
              }).catch((error) => {
                console.log("Error removing document: ", error);
              });
        });
      });

      // Write Documents
      request(Options, function(error, response) {
        if (error) throw new Error(error);
        const apiResponse = JSON.parse(response.body);
        const parsedResponse = apiResponse["news_results"];
        for (let i = 0; i < parsedResponse.length; i++) {
          // console.log(i);
          db.collection("myCollectionName").add(parsedResponse[i]);
        }
      });
  }));

Upvotes: 0

Views: 718

Answers (1)

Renaud Tarnec
Renaud Tarnec

Reputation: 83093

request supports callback interfaces natively but does not return a promise, which is what you must do within a Pub/Sub Cloud Function (as well as within background Cloud Functions).

This is explained in the official Firebase video series here: https://firebase.google.com/docs/functions/video-series/. In particular watch the three videos titled "Learn JavaScript Promises" (Parts 2 & 3 especially focus on background triggered Cloud Functions, but it really worth watching Part 1 before).

There is a request-promise library that could be used instead, but since Feb 11th 2020 request is fully deprecated, so I would recommend using axios.

The following code should do the trick (untested):

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const axios = require('axios');

admin.initializeApp();
const db = admin.firestore();

exports.scheduledPRRfunction = functions.pubsub
    .schedule("every 2 minutes")
    .timeZone("America/New_York")
    .onRun(async (context) => {

        try {

            const querySnapshot = await db.collection("myCollectionName").get();
            const promises = querySnapshot.docs.map(doc => db.collection("myCollectionName").doc(doc.id).delete());

            await Promise.all(promises);

            const options = {
                "method": "get",
                "url": "...",
                "headers": {
                    "Cookie": ".....",
                },
            };

            const axiosResponse = await axios(options);
            const apiResponse = JSON.parse(axiosResponse.data);
            const parsedResponse = apiResponse["news_results"];

            const docCreationPromises = parsedResponse.map(response => db.collection("myCollectionName").add(parsedResponse))

            await Promise.all(docCreationPromises);

            return null;

        } catch (error) {
            console.log(error);
            return null;
        }

    });

Note that:

  • We use async/await, to make it easier to write the asynchronous code
  • Since you are triggering several asynchronous operations in parallel you have to use Promise.all().

In addition, note that since Version 1.0.0 of the Firebase SDK for Cloud Functions, firebase-admin shall be initialized without any parameters within the Cloud Functions runtime, as follows:

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

Upvotes: 3

Related Questions