0

I have a firestore project that manages user bookings and includes the cloud function below that is set to run daily at midnight. The function identifies flags for recurring bookings and creates a new booking (firestore document) for the same time/day the following week.

This function runs correctly probably 5 or 6 days a week but randomly doesn't run some days. It's called because I see the 'Daily Recurring Bookings Started...' log but no new documents are created.

If I then change the time on the pubsub schedule for it to run again later n the day it works...

No errors in the logs...

Struggling to understand why the function seems to be killed on some occasions....


// Scheduled function to create repeat bookings each day.
exports.createRecurringBooking = functions.pubsub
    .schedule('01 00 * * *')
    .timeZone('Europe/London') // Runs in London Timezone
    .onRun((context) => {
        const firestore = admin.firestore;
        const db = admin.firestore();

        console.log('⏰ Daily Recurring Bookings Started....');


        // Define current day and next day with 00:00 start Time

        const today = new Date(new Date().setHours(0, 0, 0, 0));
        const todayUnix = dayjs(today).valueOf();
        const tomorrowUnix = dayjs(today).add(1, 'day').valueOf();
        const todayTimestamp = firestore.Timestamp.fromMillis(todayUnix);
        const tomorrowTimestamp = firestore.Timestamp.fromMillis(tomorrowUnix);


        // Get bookings from firestore for current day with no end date

        db.collection('bookings')
            .where('startTime', '>=', todayTimestamp)
            .where('startTime', '<', tomorrowTimestamp)
            .where('isRecurring', '==', true)
            .where('recurringCount', '==', -1)
            .get()
            .then((querySnapshot) => {
                querySnapshot.forEach((doc) => {

                    const startTime = dayjs(doc.data().startTime.toDate());
                    const newStartTime = dayjs(startTime).add(7, 'day');
                    const newStartTimeTimestamp = firestore.Timestamp.fromMillis(
                        dayjs(newStartTime).valueOf()
                    );

                    newRecurringBooking = {
                        bookingDetails: doc.data().bookingDetails,
                        channel: doc.data().channel,
                        createdAt: firestore.Timestamp.now(),
                        isRecurring: true,
                        start: doc.data().start ? doc.data().start : newStartTimeTimestamp,
                        location: doc.data().location,
                        recurringCount: doc.data().recurringCount,
                        recurringDescription: doc.data().recurringDescription
                            ? doc.data().recurringDescription
                            : '',
                        recurringWeek: doc.data().recurringWeek + 1,
                        startTime: newStartTimeTimestamp,
                        status: 'confirmed',
                        studio: doc.data().studio,
                        user: doc.data().user,
                    };

                    db.collection('bookings')
                        .doc()
                        .set(newRecurringBooking)
                        .then(() => {
                            console.log(' ⭐ Recurring Booking created! ');
                        })
                        .catch((error) => {
                            console.error('Error Creating Booking: ', error);
                        });
                });
            })
            .catch((error) => {
                console.log(
                    `❌ ERROR when creating open ended recurring bookings! Error: ${error}`
                );
            });


        // Get bookings from firestore for current day with end date

        db.collection('bookings')
            .where('startTime', '>=', todayTimestamp)
            .where('startTime', '<', tomorrowTimestamp)
            .where('isRecurring', '==', true)
            .get()
            .then((querySnapshot) => {
                querySnapshot.forEach((doc) => {

                    const startTime = dayjs(doc.data().startTime.toDate());
                    const newStartTime = dayjs(startTime).add(7, 'day');
                    const newStartTimeTimestamp = firestore.Timestamp.fromMillis(
                        dayjs(newStartTime).valueOf()
                    );

                    // Only create new booking if sequence hasn't ended.
                    if (
                        doc.data().recurringWeek < doc.data().recurringCount &&
                        doc.data().recurringCount > 0
                    ) {
                        
                        newRecurringBooking = {
                            bookingDetails: doc.data().bookingDetails,
                            channel: doc.data().channel,
                            createdAt: firestore.Timestamp.now(),
                            isRecurring: true,
                            location: doc.data().location,
                            recurringCount: doc.data().recurringCount,
                            recurringDescription: doc.data().recurringDescription
                                ? doc.data().recurringDescription
                                : '',
                            recurringWeek: doc.data().recurringWeek + 1,
                            startTime: newStartTimeTimestamp,
                            status: 'confirmed',
                            studio: doc.data().studio,
                            user: doc.data().user,
                        };

                        db.collection('bookings')
                            .doc()
                            .set(newRecurringBooking)
                            .then(() => {
                                console.log(' ⭐  Booking created! ');
                            })
                            .catch((error) => {
                                console.error('Error Creating Booking: ', error);
                            });
                    }
                });
            })
            .catch((error) => {
                console.log(
                    `❌ ERROR when creating recurring bookings with end date! Error: ${error}`
                );
            });

        
        return null;
    });
Olly
  • 157
  • 6
  • From your code, a new document is only created when the ```querySnapshot``` actually has documents inside it. Maybe the query doesn't find any documents matching your parameters? In addition to that I'm not sure if forEach actually waits for your promise to finish. You should use ```for...of``` https://stackoverflow.com/questions/53892863/how-to-wait-a-promise-inside-a-foreach-loop – SRR Jan 18 '22 at 12:28

1 Answers1

0

You are not at all taking into account the asynchronous character of the Firebase method you call in your Cloud Function. I don't know if this is the exact cause of your problem but it will create problems one day or the other, in a way that is difficult to debug because it will appear in an erratic manner as you explained in your question.

As you will see in the three videos about "JavaScript Promises" from the official Firebase video series you must return a Promise or a value in a background triggered Cloud Function to indicate to the platform that the Cloud Function is finished. Since you are not returning a Promise, you encounter this inconsistent behavior. Sometimes the Cloud Function is killed by the platform before it has written to Firestore and sometimes the Cloud Function platform does not terminate the Function immediately and the asynchronous operation can be completed.

You need to adapt your code using async/await or Promise.all(). If you want to wait for each Promise to resolve before initializing the next, you can do this by awaiting each of the Promises inside an async function. If you do not want to wait for each promise to finish before starting the next, you can use Promise.all() to run something after all your promises have resolved.

Priyashree Bhadra
  • 3,182
  • 6
  • 23