0

I have a constructor for ChatRoom that looks like this:

class ChatRoom {
    constructor(title, userA, userB, advertId, onComplete, fromRef) {

        this.members = [{
            userId: userA.userId,
            username: userA.username || '',
            photo: userA.photo || '',
        }, {
            userId: userB.userId,
            username: userB.username || '',
            photo: userB.photo || ''
        }]; 

        this.createdAt = Date.now()
        this.advertId = advertId
        this.isRemoved = false;
        if (_.isEmpty(fromRef)) {
            this._sendTofirebase(onComplete)
        } else {
            this.chatRoomRef = fromRef
        }

    }

The function of interested is called sendTofirebase which is places on the 3rd line from the bottom. The function has this code:

_sendTofirebase(onComplete) {
        this._checkDuplicateChat((isDuplicate) => {
          if (isDuplicate)
          {
                var error = new ChatRoomError("Chat room was not created")
                error.code = '409_DUPLICATE_RECORD_FOUND'
                throw error
          }
          // create chat room reference in firebase
          this.chatRoomRef = firebase().ref('ChatRooms/').push({
              title: this.title,
              advertId: this.advertId,
              members: this.members,
              createdAt: this.createdAt,
              isRemoved: this.isRemoved,
          }, onComplete);
          var chatKey = this.chatRoomRef.key
          // create user chat reference in firebase
          this.UserChatRef = firebase().ref("UsersChat").child(this.members[0].userId).child(chatKey).set({"lastModified":Date.now(), "lastMessage" : ''}, (err) => {
              if (err) {
                  throw new ChatRoomError(err)
              }
          });
      
          // create user chat reference in firebase
          this.UserChatRef = firebase().ref("UsersChat").child(this.members[1].userId).child(chatKey).set({"lastModified":Date.now(), "lastMessage" : ''}, (err) => {
              if (err) {
                  throw new ChatRoomError(err)
              }
          });
        });
      }

The function sendTofirebase will create a ChatRoom object and send it to the realtime database in Firebase. This works fine. However, in this function, I call another function called checkDuplicateChat() which is supposed to query all the ChatRoom objects and check if any ChatRoom object contains members with the same ID as the one being created. Essentially it checks for duplicate ChatRooms.

The issue here is that in the sendTofirebase() function, the entire function seems to execute, which creates the ChatRoom, before the checkDuplicateChat() can finish executing and throw an error that a duplicate has been found.

I am aware that querying databases is an asynchronous operation, I attempted to use async/await but it didn't work, I also attempted to nest the rest of the sendTofirebase() inside the callback of checkDuplicateChat() but it also did not work. I have been scratching my head over this for a couple of days, so i'd greatly appreciate any help

P.S the code for the checkDuplicateChat() function is here:

_checkDuplicateChat(onComplete) {
        const userA_ID = this.members[0].userId
        const userB_ID = this.members[1].userId
        this.chatRoomRef = firebase().ref('ChatRooms')
        var foundRoom = false
        this.chatRoomRef.once('value', (chatSnapshot) => {
             chatSnapshot.forEach((snapshot) => {
                var snap = snapshot.val()
                if ((snap.members[0].userId == userA_ID && snap.members[1].userId == userB_ID)
                    || (snap.members[0].userId == userB_ID && snap.members[1].userId == userA_ID)) {
                        foundRoom = true;
                    }
            })
            onComplete(foundRoom);
        })
    }
Ahmet-Salman
  • 194
  • 8

1 Answers1

0

That is the expected behavior, since _checkDuplicateChat perform asynchronous work (just like _sendTofirebase itself does). You will need to pass a separate callback into _checkDuplicateChat, just like (but separate from) the one you pass into _sendTofirebase already:

_sendTofirebase(onComplete) {
  this._checkDuplicateChat((isDuplicate) => { // 
    // TODO: create the room based on the value of isDuplicate

    // create chat room reference in firebase
    this.chatRoomRef = firebase().ref('ChatRooms/').push({
        title: this.title,
        advertId: this.advertId,
        members: this.members,
        createdAt: this.createdAt,
        isRemoved: this.isRemoved,
    }, onComplete);

    var chatKey = this.chatRoomRef.key
    // create user chat reference in firebase
    this.UserChatRef = firebase().ref("UsersChat").child(this.members[0].userId).child(chatKey).set({"lastModified":Date.now(), "lastMessage" : ''}, (err) => {
        if (err) {
            throw new ChatRoomError(err)
        }
    });

    // create user chat reference in firebase
    this.UserChatRef = firebase().ref("UsersChat").child(this.members[1].userId).child(chatKey).set({"lastModified":Date.now(), "lastMessage" : ''}, (err) => {
        if (err) {
            throw new ChatRoomError(err)
        }
    });
  });
}

For this to work, your _checkDuplicateChat then needs to call its callback with the result of the asynchronous operation. When we also remove the duplicate data loading from that function, it ends up like this:

_checkDuplicateChat(onComplete) {
    const userA_ID = this.members[0].userId
    const userB_ID = this.members[1].userId
    this.chatRoomRef = firebase().ref('ChatRooms')
    val foundRoom = false
    this.chatRoomRef.on('value', (chatSnapshot) => {
         chatSnapshot.forEach((snapshot) => {
            var snap = snapshot.val()
            if ((snap.members[0].userId == userA_ID && snap.members[1].userId == userB_ID)
                || (snap.members[0].userId == userB_ID && snap.members[1].userId == userA_ID)) {
                    foundRoom = true;
                }
        })
        onComplete(foundRoom);
    })
}

Since you seem uncomfortable with asynchronous operations, I recommend reading up on those with:

Also: a more effective structure for organizing chat rooms can be found in Best way to manage Chat channels in Firebase

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Hey, appreciate the sources and solution. However, when I implement your solution I lose reference to the _this._ operator for some reason. It keeps giving me _Cannot read property 'title' of undefined_ error. What could be a solution here? – Ahmet-Salman Feb 25 '23 at 10:12
  • That was already present in the code in your question. I changed it to use fat arrow functions (`=>`) everywhere. – Frank van Puffelen Feb 25 '23 at 15:21