Emilis
Emilis

Reputation: 162

Firebase function async method returns undefined

enter image description here

Hello, I have made Firebase function which is watching if users matched. All parts work, but i added one more method getUserDataById where i want to get extra data from users, it returns undefined. So this is what i tried:

exports.UserPressesLike = functions.database
  .ref('/users/{userId}/matches/{otherUserId}')
  .onCreate(async (snapshot, context) => {
    // Grab the current value of what was written to the Realtime Database.
    const original = snapshot.val();
    const userId = context.params.userId;
    const matchedUserId = context.params.otherUserId;
    const a = await checkUserMatch(userId, matchedUserId);
    if (a === true) {
      console.log('Its a match');
      addNewChat(userId, matchedUserId);
      //create chat for both users
    } else {
      console.log('There is no match');
      //do nothing
      console.log(a);
    }

    return null;
  });

checkUserMatch = async (userId, matchedUserId) => {
  const isLiked = await admin
    .database()
    .ref('/users/' + matchedUserId + '/matches/' + userId)
    .once('value')
    .then(snapshot => {
      // let tempuserId = snapshot.val();
      // if()
      let isLiked = snapshot.exists();
      console.log(isLiked);
      return isLiked;
    })
    .catch(error => {
      console.log(error);
      return snapshot;
    });
  return isLiked;
};

addNewChat = async (userId, matchedUserId) => {
  const user1 = await getUserDataById(userId);
  const user2 = await getUserDataById(matchedUserId);
  console.log(JSON.stringify('User data: ' + user1));
  const snapshot = await admin
    .database()
    .ref('/chats')
    .push({
      members: { [userId]: true, [matchedUserId]: true },
      [userId]: { username: [user1.username] },
      [matchedUserId]: { username: [user2.username] },
    });
};

getUserDataById = async userId => {
  const snapshot = await admin
    .database()
    .ref('/users/' + userId)
    .once('value')
    .then(childsnapshot => {
      let data = childsnapshot.val();
      return data;
    });
};

But I get error:

TypeError: Cannot read property 'username' of undefined
    at addNewChat (/srv/index.js:93:36)
    at <anonymous>
    at process._tickDomainCallback (internal/process/next_tick.js:229:7)

The problem is in getUserDataById method. Because it returns undefined. Where I made mistake?

Why I get username: { 0 : emilis} it should be username: emilis?? enter image description here

Upvotes: 0

Views: 281

Answers (1)

samthecodingman
samthecodingman

Reputation: 26171

Part 1: getUserDataById returns undefined

You forgot return snapshot in your async function. (However, as it as a plain object, not a snapshot, I would rename the variable)

getUserDataById = async userId => {
  const userData = await admin
    .database()
    .ref('/users/' + userId)
    .once('value')
    .then(childsnapshot => {
      let data = childsnapshot.val();
      return data;
    });
  return userData;
};

However, I would flatten it to the following (which is identical to the above, yet concise):

getUserDataById = userId => {
  return admin
    .database()
    .ref('/users/' + userId)
    .once('value')
    .then(childsnapshot => childsnapshot.val());
};

Part 2: Why is my data returned as {username: {0: "Emilis"}}?

{0: "Emilis"} being returned as an object, not an array, is caused by how Firebase stores arrays in the Realtime Database. There is quite a comprehensive article on arrays on the Firebase Blog covering these quirks which I recommend reading. I'll summarise the key ones here.

When any array is stored in the Realtime Database it is stored in it's object form where {username: [user1.username] } will be stored as {username: {0: "someusername"} }. Because JSON is typeless, the Realtime Database no longer understands this entry to be an array. An array with multiple entries will also be stored stored as a plain object ([value1, value2] will become {0: value1, 1: value2}).

When the Firebase JavaScript SDK downloads data from the Realtime Database, it checks the keys of any objects for a mostly sequential numeric sequence (0,1,2,3,... or 0,1,3,4,...) and if detected, converts it to an array using null for any missing entries.

As {0: value1, 1: value2} contains the sequential keys 0 and 1, it will be parsed as [value1, value2].

As {0: "someusername"} does not contain a sequence of keys, it is returned as-is.

To bypass this, remove the single entry array and use it's value directly (as below) or explicitly convert it to an array in your client code.

addNewChat = async (userId, matchedUserId) => {
  const user1 = await getUserDataById(userId);
  const user2 = await getUserDataById(matchedUserId);
  console.log(JSON.stringify('User data: ' + user1));
  return admin // don't forget to return the Promise!
    .database()
    .ref('/chats')
    .push({
      members: { [userId]: true, [matchedUserId]: true }, // FYI: these are "use value as the key" instructions not arrays.
      [userId]: { username: user1.username },
      [matchedUserId]: { username: user2.username },
    });
};

Upvotes: 1

Related Questions