0

The following code is for my FCM function where I am listening to firestore the getting tokens before constructing payload to send. Every time its sent the system logs that the tokens are empty. How can I make sure its not empty when sending the fcm?

let functions = require('firebase-functions');

let admin = require('firebase-admin');

admin.initializeApp(functions.config().firebase);

exports.sendNotification =functions.firestore.document('chatrooms/{chatRoomId}/messages/{messageId}')
.onWrite((snap, context) => {

    let message = snap.after.data().messageBody;
    let messageSender = snap.after.data().senderName;
    let messageUserId = snap.after.data().userId;
    let chatRoomId = context.params.chatRoomId;

    let tokens = [];
    let chatRoomRef = admin.firestore().collection("chatrooms").doc(chatRoomId);

    return admin.firestore().runTransaction(t => {
        return t.get(chatRoomRef)
            .then(chatroom => {
                let usersArray = chatroom.data().chatMembers;
                usersArray.forEach(user_id => {
                    let userIdRef = admin.firestore().collection("tokens").doc(user_id);
                    return t.get(userIdRef).then(doc => {
                        if (doc.exists) {
                            let user_token = doc.data().token;
                            functions.logger.log('token: ', token);
                            tokens.push(user_token);
                        }
                    }).catch(err => {
                        functions.logger.error(err);
                    })
                });
            });
    }).then(() => {
        //The transaction has run successfully, we expect tokens array to not be empty
        functions.logger.log("Construction the notification message.");
        const payload = {

            data: {
                data_type: "data_type_chat_message",
                title: "Tuchat",
                message: message,
                sender_id: messageUserId,
                sender_name: messageSender,
                chatRoom_id: chatRoomId
            }
        };
        const options = {
            priority: "high",
            timeToLive: 60 * 60 * 24
        };

        return admin.messaging().sendToDevice(tokens, payload).catch(err => {
            functions.logger.error(err);
        });
    }).catch(err => {
        functions.logger.error('Transaction error: ', err);
    })
});

Also before trying transactions it was returning empty tokens.

ronnieotieno
  • 134
  • 2
  • 11

1 Answers1

1

The problem is because of the way you're dealing with promises inside the forEach loop. The loop will not wait for promises to be resolved for the code inside it. It is currently just iterating as fast as possible over each user ID and not waiting for the queries to complete inside it. That means the code will continue before tokens can get populated.

You should instead collect each of the promises inside the loop and use Promise.all() to wait for the entire batch, then return the promise from that to indicate when all the work is complete. That will ensure that tokens contains everything you want before the next then in the chain is executed.

The general form of the code will be like this:

.then(() => {
    const promises = []
    collection.forEach(item => {
        const promise = doSomeWork()
        promises.push(promise)
    })
    return Promise.all(promises)
})
.then(() => {
    // this will continue only after all promises are resolved
})

See also:

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441