Search code examples
node.jsfirebasegoogle-cloud-firestoregoogle-cloud-functionsfirebase-admin

firebase cloud functions invoke callback


I'm using firebase admin SDK to create new User to my web app.

// function to create user and store additional info in firestore
exports.createUser = functions
    .https.onCall((data, context) => {
      admin.auth().createUser({
        phoneNumber: data.phoneNumber,
        displayName: data.displayName,
      }).then((user) => {
        // store data in firestore
        admin.firestore().collection("user").doc(user.uid).set({...data});
      }).catch((err) => {
        // handle error
      });
    });

When I call the function from the client (attach it to onClick event) I want to wait till user is successfully added in firestore and invoke the fetchUser function so I can see the new list of data with the newly added user. Currently fetchUser gets called and page refreshes but I cannot see the newly added user before refreshing it.

const createUser = functions.httpsCallable('createUser');

const createNewUser = () => {
    createUser({
      phoneNumber: "+1111111111111",
      displayName: "test",
      introduction: "testcreate",
    })
  .then((res) => {
    fetchUser(); // fetchUser just fetches user data from firestore and rerenders page 
  })
  .catch((err) => {
    console.log(err);
  });

};

To summarize, can I know when cloud function will complete its job and run a particular function or task ?


Solution

  • 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, Pub/Sub or Callable Cloud Function when all the asynchronous operations are complete.

    This has two linked effects:

    1. When the asynchronous operations are complete, it indicates to the Cloud Function platform that it can terminate and clean up your function.
    2. On the opposite, until the asynchronous operations are complete, it indicates to the Cloud Function platform that it should wait before terminating the Cloud Function.

    To return a Promise, since you chain several asynchronous Firebase Admin SDK methods (which return Promises), you need to return the Promise chain as follows:

    exports.createUser = functions.https.onCall((data, context) => {
        return admin  // <== See return here
        .auth()
        .createUser({
          phoneNumber: data.phoneNumber,
          displayName: data.displayName,
        })
        .then((user) => {
          // store data in firestore
            return admin   // <== See return here
            .firestore()
            .collection('user')
            .doc(user.uid)
            .set({ ...data }); 
        })
        .then(() => {
          // The set() method returns Promise<void>
          // To send data back to the client, return data that can be JSON encoded
          return { result: 'user created' };
        })
        .catch(error => {
          // See https://firebase.google.com/docs/functions/callable#handle_errors
          // on how to handle errors
        });
    });
    

    This way, when your front-end gets back the Callable Cloud Function response, you are sure that the user and the Firestore document were created.


    If you want to use the async/await keywords, do as follows. Note that it is not recommended to mixup then() with async/await.

    exports.createUser = functions
        .https.onCall(async (data, context) => {   // <== See async here
    
            try {
                const user = await admin.auth().createUser({
                    phoneNumber: data.phoneNumber,
                    displayName: data.displayName,
                });
    
                await admin.firestore().collection("user").doc(user.uid).set({ ...data });
                return { result: 'user created' }
                
            } catch (error) {
                console.log(error);  // If desired
                // See https://firebase.google.com/docs/functions/callable#handle_errors
                // on how to handle errors
            }
    
        });
    

    See how the code is much more readable, as if it was synchronous code.