Search code examples
swiftfirebasegoogle-cloud-platformgoogle-cloud-functionsfirebase-cloud-messaging

Problem with executing asynchronous Cloud Function


My database looks like this

enter image description here

My cloud function is

// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp();

exports.userGotNewMessage = functions
.region(`europe-west1`)
.database
.ref(`user-messages/{userId}/{senderId}/{messageId}`)
.onCreate((snap, context) => {
  var userId = context.params.userId
  console.log(`Step 1  ${userId}`)
  var text = snap.val().text
  var toId = snap.val().toId
  var numberOfUnreadMessages = 0
  var db = admin.database()

  if (userId === toId) {
    console.log(`Step 2 ${userId}`)
  var unreadMessagesRef = db.ref(`unread-messages/${userId}`)
  unreadMessagesRef.on("value", (snap) => {
    snap.forEach((childNode) => {
      var nodeNumber = childNode.val().numberOfUnreadMessages
      numberOfUnreadMessages = numberOfUnreadMessages + nodeNumber
    })
    return console.log(`Found ${numberOfUnreadMessages} unread messages for ${userId}`)
  });

  console.log(`Step 3 ${userId}`)
  var token = 'dxfAkmce.....my testing device'
  console.log(text)
  var message = {
    "token": String(token),
    "data": {
      "count": `${numberOfUnreadMessages}`
    }
  }

  admin.messaging().send(message)
  .then((response) => {
    console.log(`Step 4 ${userId}`)
    // Response is a message ID string.
    return console.log('Successfully sent message:', response);
    // detailed information about result if send succeded but something went wrong
    // console.log(response.results[0].error);
  })
  .catch((error) => {
    return console.log('Error sending message:', error);
  });
  }
  console.log(`Step 5 ${userId}`)
  return null
});

Tying to use this function I get strange behavior. Number of unread messages is counted after message is sent and many more, for example after start I got message about the counted unread messages from nowhere(there is no 16 messages at all in database))) Something like this in console

3:05:23.627 PM userGotNewMessage Successfully sent message: projects/chatapp-2e320/messages/1544015123460374

3:05:23.626 PM userGotNewMessage Step 4 VobaLy7AKMeYnGv7OgIokaeQ5UG2

3:05:23.340 PM userGotNewMessage Function execution took 9 ms, finished with status: 'ok'

3:05:23.334 PM userGotNewMessage Step 5 nx9XfqgIqyS8PdZ8PzLQ9sEyKoV2

3:05:23.333 PM userGotNewMessage Step 1 nx9XfqgIqyS8PdZ8PzLQ9sEyKoV2

3:05:23.331 PM userGotNewMessage Function execution started

3:05:23.325 PM userGotNewMessage Function execution took 151 ms, finished with status: 'ok'

3:05:23.317 PM userGotNewMessage Step 5 VobaLy7AKMeYnGv7OgIokaeQ5UG2

3:05:23.317 PM userGotNewMessage hello again

3:05:23.317 PM userGotNewMessage Step 3 VobaLy7AKMeYnGv7OgIokaeQ5UG2

3:05:23.317 PM userGotNewMessage Found 1 unread messages for VobaLy7AKMeYnGv7OgIokaeQ5UG2

3:05:23.234 PM userGotNewMessage Step 2 VobaLy7AKMeYnGv7OgIokaeQ5UG2

3:05:23.234 PM userGotNewMessage Step 1 VobaLy7AKMeYnGv7OgIokaeQ5UG2

3:05:23.182 PM userGotNewMessage Found 16 unread messages for VobaLy7AKMeYnGv7OgIokaeQ5UG2

3:05:23.175 PM userGotNewMessage Function execution started

I understand that this is caused by asynchronous work, but I can't fix it by myself, because I am a complete beginner. Please help me fix this bugs!!


Solution

  • As @rijin said in his answer, you should return the promise returned by the asynchronous send() method. But, as important, you should not return null at the end of the Cloud Function.

    By returning null, which will happens before the promises returned by send() resolves, you are indicating to the Cloud Function that the work is finished. So, in other words, the Cloud Function stops executing before the asynchronous job is finished.

    Also, using an on() listener in a Cloud Function (which has a relatively "short" life) is not really adequate. You should better use the once() method.

    Finally, you are apparently implementing two different business logic parts in your Cloud Function, one part for reporting a number of unread messages and another part for sending the message. You should either do that in two different Cloud Functions or chain the different promises returned by the asynchronous methods (i.e. the once() and send() methods).

    So, for the message sending part, doing as follows should work:

    exports.userGotNewMessage = functions
    .region(`europe-west1`)
    .database
    .ref(`user-messages/{userId}/{senderId}/{messageId}`)
    .onCreate((snap, context) => {
      var userId = context.params.userId
      console.log(`Step 1  ${userId}`)
      var text = snap.val().text
      var toId = snap.val().toId
      var numberOfUnreadMessages = 0
      var db = admin.database()
    
    
      console.log(`Step 3 ${userId}`)
      var token = 'dxfAkmce.....my testing device'
      console.log(text)
      var message = {
        "token": String(token),
        "data": {
          "count": `${numberOfUnreadMessages}`
        }
      }
    
      return admin.messaging().send(message);
    
    });
    

    If you want to chain the send() and once() methods in one Cloud Function, you could do something along the following lines:

    return admin.messaging().send(message)
    .then(messageID => {
        if (userId === toId) {
            var unreadMessagesRef = db.ref(`unread-messages/${userId}`);
            return unreadMessagesRef.once('value')
                .then(snap => {
                    snap.forEach(childNode => {
                        var nodeNumber = childNode.val().numberOfUnreadMessages;
                        numberOfUnreadMessages = numberOfUnreadMessages + nodeNumber;
                    });
                    console.log(`Found ${numberOfUnreadMessages} unread messages for ${userId}`);
                    return null;
                });
        } else {
            return null;
        }
    });