0

My scheduled function



// TODO;
export const reportUsage =
  fun.pubsub.schedule("0 0 1 * *").onRun(async (context) => {
    functions.logger.debug("Initialising db");
    const db = admin.firestore();
    const users = await db.collection("users").get();
    users.docs.forEach( async (doc) => {
      functions.logger.debug("Got list of users. Looping..");
      const userData = doc.data();
      const SOME_DATA:number = userData["SOME_DATA"];
      functions.logger.debug("got SOME_DATA of this user");
      const SOME_DATAIntPart:number =
       parseInt(SOME_DATA.toFixed(20).split(".")[0]);
      const SOME_DATAFloatPart:number =
       parseFloat("0." + SOME_DATA.toFixed(20).split(".")[1]);
      const subItemId =
       userData["stripeInfo"]["subscription"]["items"]["data"][0]["id"];
      functions.logger.debug("got sub id of this user");
      await stripe.subscriptionItems.createUsageRecord(subItemId, {
        quantity: SOME_DATAIntPart,
        timestamp: admin.firestore.Timestamp.now().seconds,
      }, {
        timeout: 60,
        maxNetworkRetries: 5,
      });
      functions.logger.debug("got reported to stripe");
      await doc.ref.update({
        "SOME_DATA": SOME_DATAFloatPart,
      });
      functions.logger.debug("updated SOME_DATA");
      return null;
    });
  });

When I run the function manually from Cloud Scheduler, it returns RIGHT before the Stripe call

 await stripe.subscriptionItems.createUsageRecord(subItemId, {
        quantity: SOME_DATAIntPart,
        timestamp: admin.firestore.Timestamp.now().seconds,
      }, {
        timeout: 60,
        maxNetworkRetries: 5,
      });

The logs i'm logging show this Image showing logs in Google Cloud

As you can see, it immediately returns before executing the Stripe call.

The errors in the last log are this: Image showing the error log

However, running the functions, firestore and pubsub emulator, using the functions shell I can call the reportUsage function and this is what prints out

Image showing working logs in emulator

Can someone please tell me why the pubsub function is not working in production?

1 Answers1

2

You should not use async/await within a forEach() loop, see "JavaScript: async/await with forEach()" and "Using async/await with a forEach loop".

You can use Promise.all() as follows:

export const reportUsage = fun.pubsub
  .schedule('0 0 1 * *')
  .onRun(async (context) => {
    functions.logger.debug('Initialising db');

    const db = admin.firestore();
    const users = await db.collection('users').get();

    users.docs.forEach((doc) => {
      functions.logger.debug('Got list of users. Looping..');
      const userData = doc.data();
      const SOME_DATA: number = userData['SOME_DATA'];
      functions.logger.debug('got SOME_DATA of this user');
      const SOME_DATAIntPart: number = parseInt(
        SOME_DATA.toFixed(20).split('.')[0]
      );
      const SOME_DATAFloatPart: number = parseFloat(
        '0.' + SOME_DATA.toFixed(20).split('.')[1]
      );
      const subItemId =
        userData['stripeInfo']['subscription']['items']['data'][0]['id'];
      functions.logger.debug('got sub id of this user');

      const promises = [];
      // We push a Promise to the promsies Array. Note that the then() method returns a promise.
      promises.push(
        stripe.subscriptionItems
          .createUsageRecord(
            subItemId,
            {
              quantity: SOME_DATAIntPart,
              timestamp: admin.firestore.Timestamp.now().seconds,
            },
            {
              timeout: 60,
              maxNetworkRetries: 5,
            }
          )
          .then(() => {
            return doc.ref.update({
              SOME_DATA: SOME_DATAFloatPart,
            });
          })
      );

      functions.logger.debug('updated SOME_DATA');
    });

    await Promise.all(promises);
    return null;
  });
Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
  • Thanks for the answer! However, I got another problem now with no detailed logs to fix the problem. ```Function execution took 4366 ms, finished with status: 'error'``` after logging functions.logger.debug('got SOME_DATA of this user'); This might be because some data is NULL in the database, so I will try a bit and come back to you. – Bashar Shehab Nov 03 '21 at 22:01
  • Update: Works great! Thanks a lot!! For people looking at this in the future, if there is some NULL data and you need to skip an iteration of the forEach loop, `return` instead of `continue` – Bashar Shehab Nov 04 '21 at 00:57