Ad

Firebase Functions Sporadically Updating Doc

I´m receiving Webhooks from an online store about categories created. The following function creates the document inside the collection categories

exports.createProductWebhookCategory = functions
    .https.onRequest(async (request, response) => {
        var category = request.body.product_type;
        try {
            const categoryRef = admin.firestore().collection("categories").doc(`${category}`);
            await categoryRef.set({
                name: category,
            });
            response.status(200).send("Done");
        } catch (error) {
            console.log(error);
            response.status(400).send("Error Cat");
        }
    });

After the category is created I'm calling an API to create an item inside Webflow. With the returned promise I get the item Id I want to store inside the previous created document

I´m trying this with 5 categories (5 webhooks) and just 1 or 2 out of 5 are updated. The other documents are not updated and do not contain the webflowId field. The updated ones change with each test run. Anybody an idea what I'm doing wrong?

exports.onCreateCategoryCallback = functions
    .runWith({ failurePolicy: true })
    .firestore
    .document("/categories/{categoryId}")
    .onCreate(async (snapshot, context) => {
        const cat = snapshot.data();
        const docId = context.params.categoryId;
        const categoryRef = admin.firestore().collection("categories").doc(docId);
        try {
            const webflow_item = await webflow.createItem({
                collectionId: 'xxx',
                fields: {
                    'name': cat.name,
                    '_archived': false,
                    '_draft': false,
                },
            }, { live: true });
            console.log(`ItemId for Cat ${cat.name} is ${webflow_item._id}`);
            const doc = await categoryRef.get();
            console.log(doc.data());
            const res = await categoryRef.update({
                webflowId: webflow_item._id
            });
            console.log(`RES for ${cat.name} is: `, res);
            console.log("Function complete for cat: ", cat.name);
        } catch (error) {
            console.log(error);
            throw 'error';
        }
    });

Console logs for both unsuccessful and successful update are the following

ItemId for Cat Coffee is 620fdc8858462f33735c986

{ name: 'Coffee' } 

RES for Coffee is:  WriteResult { 
_writeTime: Timestamp { _seconds: 1645206666, _nanoseconds: 686306000 } 
} 

Function complete for cat:  Coffee 
Ad

Answer

The problem most probably comes from the fact that you are not correctly managing the life cycle of your second Cloud Function (the Firestore triggered one).

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 when all the asynchronous operations complete. This way you indicate to the Cloud Function platform that it can shut down the instance running your Cloud Function and you also avoid this instance is shut down before the asynchronous operations are done.

Concretely, it happens sometimes that your Cloud Function is terminated before the asynchronous operations are done, because you don't return a Promise or a value at the end of your code. The other times, the Cloud Function platform does not terminate the Function immediately and the asynchronous operations can complete. You don't have any control on this behaviour and therefore it appears as an erratic behaviour and is difficult to understand/debug.

So the following adaptations should do the trick (untested):

exports.onCreateCategoryCallback = functions
    .runWith({ failurePolicy: true })
    .firestore
    .document("/categories/{categoryId}")
    .onCreate(async (snapshot, context) => {
        const cat = snapshot.data();
        // const docId = context.params.categoryId;
        // const categoryRef = admin.firestore().collection("categories").doc(docId);
        
        // You can replace the two above lines by the following one
        const categoryRef = snapshot.ref; 
        
        try {
            const webflow_item = await webflow.createItem({
                collectionId: 'xxx',
                fields: {
                    'name': cat.name,
                    '_archived': false,
                    '_draft': false,
                },
            }, { live: true });
            console.log(`ItemId for Cat ${cat.name} is ${webflow_item._id}`);
            
            // Not sure why you have the two next lines?? At this stage doc.data() ===  snapshot.data()
            //const doc = await categoryRef.get();
            //console.log(doc.data());
            
            const res = await categoryRef.update({
                webflowId: webflow_item._id
            });
            console.log(`RES for ${cat.name} is: `, res);
            console.log("Function complete for cat: ", cat.name);
            
            return null;  // <== Here return a value when all the asynchronous operations complete
            
        } catch (error) {
            console.log(error);
            return null;
        }
    });

Following the comments above, the code for using a Transaction while creating the Category doc is as follows (again I didn't test it):

exports.createProductWebhookCategory = functions
    .https.onRequest(async (request, response) => {
        var category = request.body.product_type;
        try {

            const categoryRef = admin.firestore().collection("categories").doc(`${category}`);

            await admin.firestore().runTransaction((transaction) => {

                return transaction.get(categoryRef).then((doc) => {
                    if (!doc.exists) {
                        transaction.set(categoryRef, {
                            name: category,
                        })
                    }
                });
            })
            response.status(200).send("Done");
        } catch (error) {
            console.log(error);
            response.status(400).send("Error Cat");
        }
    });
Ad
source: stackoverflow.com
Ad