0

I'm creating a web app with Vue.js (this is the first time I use it). The app is basically a multi user real time quiz, in which every user have to choose a role and to answer questions related with his role. I use a collection in cloud firestore database to store questions associated to each role and answers associated to each question. Moreover each answer is characterized by a field "nextQuestion" that contains the id of the next question to visualize, a field "nextUser" that contains the id of the next user at which this new question is related (these fields are used in queries to select the next question and its possible answers) and a boolean field "default" that indicates, if true, the answer that is chosen in the case user don't answer the question within the set time (others to a field indicating the text of the answer). I get questions and answers with a query to visualize them on the webapp. My problem is related to the situation in which the user doesn't answer a question within the set time (meanwhile if a user selects an answer within the set time, I haven't problems). When the time for an answer expires, I call this function:

CountTerminated: function () {    

  if(this.optionSelected == false){   //optionSelected is a component variable that informs if a user has selected or not an answer

    this.onNotSelectedAnswer(this.getDefaultAnswer())

  }

  else{
              this.onClickButtonConfirm()  //function called if the user selects an answer within the set time
      }  

}

The function getDefaultAnswer() gets the fields (among which "nextUser" and "nextQuestion") of the default answer associated with the current question (through a query) and return them through a variable:

 getDefaultAnswer(){

            var data 
            db.collection("Utenti").doc(this.userId).collection("Domande").doc(this.questionId).collection("Risposte").where("default","==",true).get().then(querySnapshot =>{

                querySnapshot.forEach(doc=>{


                data = doc.data()

              })
            })

            return data

          },

the function onNotSelectedAnswer(data) mainly takes in input the value returned by getDefaultAnswer(), it assigns "data" to the component variable answerId and it updates the value of the component variable "userId" (that informs about the role of the user who have to answer the current question),the value of the component variable "questionId" (that contains the id of the current question) and the value of questionValue(that contains the text of the current question) using functions setUserId(), setQuestionId(), setQuestionValue()

onNotSelectedAnswer: function(data){


            if(this.userChoice == this.userId){

              this.answerId = data


            this.setUserId(this.answerId.nextUser)
            this.setQuestionId(this.answerId.nextQuestion)
            this.setQuestionValue()

            this.optionSelected = false
            this.answers=[]
            this.isActive = ""
            this.getAnswers()   //function used to query  (using updated values of userId and questionId variable) the DB to obtain a question and its related possible answers
            var a = this.channel.trigger('client-confirmEvent',{ user:  this.userId, question : this.questionId, questionval: this.questionValue})   
            console.log(a)


            }

 }

The problem is related to the fact that in onNotSelectedAnswer() function, answerId is "undefined" instead of containing the result of the query and therefore the data that I will use to upload the new question. I don't understand which is the error, I hope that you can help me.

1
  • on getDefaultAnswer(), I suggest you check if querySnapshot variable is not null before use on your method. Commented Jan 22, 2020 at 20:00

1 Answer 1

1

The problem is that the Firestore query is asynchronous but you aren't waiting for the response before continuing. Effectively what you have is this:

getDefaultAnswer () {
  var data 

  // This next bit is asynchronous
  db.doLotsOfStuff().then(querySnapshot => {
    // This callback won't have been called by the time the outer function returns
    querySnapshot.forEach(doc => {
      data = doc.data()
    })
  })

  return data
},

The asynchronous call to Firestore will proceed in the background. Meanwhile the rest of the code in getDefaultAnswer will continue to run synchronously.

So at the point the code reaches return data none of the code inside the then callback will have run. You can confirm that by putting in some console logging so you can see what order the code runs in.

The use of then to work with asynchronous code is a feature of Promises. If you aren't already familiar with Promises then you should study them in detail before going any further. Here is one of the many guides available:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises

The bottom line is that you cannot force the getDefaultAnswer method to wait for the asynchronous action to complete. What you can do instead is to return a suitable Promise and then wait for that Promise to resolve before you call onNotSelectedAnswer. It might look something like this:

getDefaultAnswer () {
  // We return the Promise chain from getDefaultAnswer
  return db.doLotsOfStuff().then(querySnapshot => {
    var data = null

    // I have assumed that forEach is synchronous
    querySnapshot.forEach(doc => {
      data = doc.data()
    })

    // This resolves the Promise to the value of data
    return data
  })
},

It is important to appreciate that the method getDefaultAnswer is not attempting to return the value of the data. It is instead returning a Promise that will resolve to the value of the data.

Within CountTerminated you would then use it like this:

this.getDefaultAnswer().then(defaultAnswer => {
  this.onNotSelectedAnswer(defaultAnswer)
})

or if you prefer:

this.getDefaultAnswer().then(this.onNotSelectedAnswer)

The latter is more concise but not necessarily clearer.

You could also write it using async/await but I wouldn't advise trying to use async/await until you have a solid grasp of how Promises work. While async/await can be very useful for tidying up code it is just a thin wrapper around Promises and you need to understand the Promises to debug any problems.

The code I've suggested above should work but there is a delay while it waits for the asynchronous request to complete. In that delay things can happen, such as the user may click on a button. That could get you into further problems.

An alternative would be to load the default answer much sooner. Don't wait until you actually need it. Perhaps load it as soon as the question is shown instead. Save the result somewhere accessible, maybe in a suitable data property, so that it is available as soon as you need it.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.