mac389
mac389

Reputation: 3133

Why is Firebase Query [ ] despite async/await?

I'm still struggling to understand how to extract values from a Firestore Query and put them into a global variable. I (now) understand that the asynchronous nature means that code isn't executed in the order that it is written. But, why is user still undefined in the following code block despite the await keyword inside of an async function? I understand (based on this question) that await should be applied only up to get(), otherwise there may not be values to iterate over.

My question is similar to this, but I run into the same issue as the comment and it looks like the question was never marked as answered. It is also similar to this, but pushing all gets to a Promise and then resolving that promise still didn't assign the data to a variable. It just printed an empty array.

I can print the variable, so my question is about assignment, I think. This function is a handler for an Intent in DialogFlow. I strongly prefer to have the data available outside of the call to the db. Adding to agent responses in a call to firestore doesn't always add the text to the agent, so I'd like to avoid that.

async function loginHandler(agent){     
     username = agent.parameters.username.name;      
     password = agent.parameters.password;

        const user = await db.collection("users")
                    .where("name","==",username)
                     .where("password","==",password)
                     .limit(1)
                     .get()
                     .then(querySnapshot =>{
                        querySnapshot.forEach(docSnapShot =>{
                            return docSnapShot.data();
                            //console.log.(docSnapShot.data()); //prints correct contents, so error is in programming logic
                            agent.add("Response"); // Would prefer to avoid this, but could refactor if I am fundamentally misunderstanding how to access Firebase data
                        })
                     })
                     .catch(error => console.log);
                                 

        console.log(user);      
       console.log("bort");  
}

Picture of Firestore to demonstrate that the correct data do exist:

enter image description here

Upvotes: 1

Views: 651

Answers (3)

Andy
Andy

Reputation: 63524

You might be able to split the code up. Return the data from the database first, then map over the data to extract the details, and then assign that result to the user variable.

async function loginHandler(agent) {

  username = agent.parameters.username.name;
  password = agent.parameters.password;

  // `await` the promise and assign it to
  // `querySnapshot`
  const querySnapshot = await db.collection('users')
    .where('name', '==', username)
    .where('password', '==', password)
    .limit(1)
    .get()
    .catch(error => console.log);
  
  const user = querySnapshot.docs.map(docSnapShot => {
    agent.add('Response');
    return docSnapShot.data();
  });

  console.log(user);

}

Upvotes: 2

I'm Joe Too
I'm Joe Too

Reputation: 5840

forEach iterates the results but doesn't return anything, so your return inside there isn't doing what you'd expect (forEach returns void so you're returning the snapshot to a function that is returning void). You can create a local variable to hold the results you iterate and then return that:

const user = await db.collection("users")
                    .where("name","==",username)
                    .where("password","==",password)
                    .limit(1)
                    .get()
                    .then(querySnapshot =>{
                        // Set up an empty array to return later
                        let data = []
                        querySnapshot.forEach(docSnapShot =>{
                            // Add each snapshot's data object to the array
                            data = [...data, ...docSnapShot.data()]
                        })
                        // Return `data` which will populate `user`
                        return data
                     })
                     .catch(error => console.log);

Upvotes: 2

ProfDFrancis
ProfDFrancis

Reputation: 9411

Have you checked what the correct answer is?

Please clarify whether you mean undefined as you say in the title, or empty array as you say in the text.

Your code looks correct to me. I would not expect the output of console.log(user) to be undefined. However, an empty list [ ] would be a perfectly reasonable answer.

Perhaps there is nobody in that collection with that username and password?

Have you tried removing the condition of equality of username and password? That should get you an element of the collection, if it is not entirely empty.

Upvotes: 1

Related Questions