Firebase Cloud Functions: Transactions function not returning promise? Code Answer

Hello Developer, Hope you guys are doing great. Today at Tutorial Guruji Official website, we are sharing the answer of Firebase Cloud Functions: Transactions function not returning promise? without wasting too much if your time.

The question is published on by Tutorial Guruji team.

here is what I am trying to do with firebase cloud function:

-Listen to any change in one of the documents under ‘user’ collection.

-Update carbon copies of the userinfo in the relevant documents in both ‘comment’ and ‘post’ collections.

Because I will need to query in relevant documents and update them at once, I am writing codes for transaction operations.

Here is the code that I wrote. It returns the error message, ‘Function returned undefined, expected Promise or value’.

exports.useInfoUpdate = functions.firestore.document('user/{userid}').onUpdate((change,context) => {
   const olduserinfo=change.before.data();
   const newuserinfo=change.after.data();
       db.runTransaction(t=>{
         return t.get(db.collection('comment').where('userinfo','==',olduserinfo))
        .then((querysnapshot)=>{
          querysnapshot.forEach((doc)=>{
             doc.ref.update({userinfo:newuserinfo})
          })
        })    
      })
  .then(()=>{
    db.runTransaction(t=>{
         return t.get(db.collection('post').where('userinfo','==',olduserinfo))
        .then((querysnapshot)=>{
          querysnapshot.forEach((doc)=>{
             doc.ref.update({userinfo:newuserinfo})
          })
        })    
      })
  })
});

I am a bit confused because as far as I know, ‘update’ method returns a promise? I might be missing something big but I picked up programming only last November, so don’t be too harsh. 🙂

Any advice on how to fix this issue? Thanks!

EDIT: Building on Renaud‘s excellent answer, I created the below code in case someone may need it. The complication with transaction is that the same data may be stored under different indices or in different formats. e.g. The same ‘map’ variable can be stored under an index in one collection, and as part of an array in another. In this case, each document returned by querying needs different update methods.

I resolved this issue using doc.ref.path, split, and switch methods. This enables application of different update methods based on the collection name. In a nutshell, something like this:

 return db.runTransaction(t => {
        return t.getAll(...refs)
            .then(docs => {
                docs.forEach(doc => {
                    switch (doc.ref.path.split('/')[0]) { //This returns the collection name and switch method assigns a relevant operation to be done.
                      case 'A':
                        t = t.update(doc.ref, **do whatever is needed for this collection**)
                        break;
                      case 'B':
                        t = t.update(doc.ref, **do whatever is needed for this collection**)
                        break;
                      default:
                        t = t.update(doc.ref, **do whatever is needed for this collection**)
                    }
                })
            })
    })

Hope this helps!

Answer

Preamble: This is a very interesting use case!!

The problem identified by the error message comes from the fact that you don’t return the Promise returned by the runTransaction() method. However there are several other problems in your code.

With the Node.js Server SDK you can indeed pass a query to the transaction’s get() method (you cannot with the JavaScript SDK). However, in your case you want to update the documents returned by two queries. You cannot call twice db.runTransaction() because, then, it is not a unique transaction anymore.

So you need to use the getAll() method by passing an unpacked array of DocumentReferences. (Again, note that this getAll() method is only available in the Node.js Server SDK and not in the JavaScript SDK).

The following code will do the trick.

We run the two queries and transform the result in one array of DocumentReferences. Then we call the runTransaction() method and use the spread operator to unpack the array of DocumentReferences and pass it to the getAll() method.

Then we loop over the docs and we chain the calls to the transaction’s update() method, since it returns the transaction.

However note that, with this approach, if the results of one of the two original queries change during the transaction, any new or removed documents will not be seen by the transaction.

exports.useInfoUpdate = functions.firestore.document('user/{userid}').onUpdate((change, context) => {
    const olduserinfo = change.before.data();
    const newuserinfo = change.after.data();

    const db = admin.firestore();

    const q1 = db.collection('comment').where('userinfo', '==', olduserinfo);  // See the remark below: you probably need to use a document field here (e.g. olduserinfo.userinfo)
    const q2 = db.collection('post').where('userinfo', '==', olduserinfo);


    return Promise.all([q1.get(), q2.get()])
        .then(results => {
            refs = [];
            results.forEach(querySnapshot => {
                querySnapshot.forEach(documentSnapshot => {
                    refs.push(documentSnapshot.ref);
                })
            });


            return db.runTransaction(t => {
                return t.getAll(...refs)
                    .then(docs => {
                        docs.forEach(doc => {
                            t = t.update(doc.ref, { userinfo: newuserinfo })
                        })
                    })
            })

        })
});

Two last remarks:

  1. I am not sure that db.collection('comment').where('userinfo', '==', olduserinfo); will be valid as olduserinfo is obtained through change.before.data(). You probably need to specify one field. This is probably the same for newuserinfo.
  2. Note that you cannot do doc.ref.update() in a transaction, you need to call the transaction’s update() method, not the one of a DocumentReference.
We are here to answer your question about Firebase Cloud Functions: Transactions function not returning promise? - If you find the proper solution, please don't forgot to share this with your team members.

Related Posts

Tutorial Guruji