1

I know this question may already be asked. But I didn't understand how things are worked.that is why I am creating the new thread.

con.query(sql,[req.params.quizId],(err,rows,fields)=>{
    //rows contains questions
    if(err) throw err;
    else{
        let object={};

        rows.forEach((item,index)=>{
            object=item;
            //here iam passing question id to get choices a async function
            getChoices(item.id)
                .then(data=>{
                    object.choices=data;
                    //save the question array 
                    response.push(object);
                    //res.send(response);
                });
        })
      res.send(response) //return empty array

    }
});

function getChoices(questionId) {
    let sql='SELECT id,text FROM `question_choices` where question_id=?';
    return new Promise((resolve, reject) => {
        con.query(sql,[questionId],(err,rows,fields)=>{
                if(err) throw err;
                else {
                    resolve(rows);
                }
        })
    })

}

I tried several things but none is worked. I think for loop didn't wait for the promise to complete and it sends the response directly. Some async problems are happening there.

I can able to get all questions from database and for each question I need to get corresponding choices that I want.

something like this

[{id:'xx', text:'yy',choices:[{id:'c',text:'kk']},etc]
Hassan Imam
  • 21,956
  • 5
  • 41
  • 51
Sreejith sreeji
  • 1,251
  • 2
  • 10
  • 18

1 Answers1

2

forEach runs synchronously. You're looking for Promise.all, which accepts an array of Promises, and resolves to an array of the resolved values once all of the Promises resolve. To transform your rows array to an array of Promises, use .map.

Also, when there's an error, you should call reject so that you can handle errors in the consumer of the Promise (the con.query callback), otherwise, when there's an error, it'll hang forever without you knowing about it:

con.query(sql,[req.params.quizId],(err,rows,fields)=>{
  if(err) throw err;
  Promise.all(rows.map((item) => (
    getChoices(item.id)
      .then((choices) => ({ ...item, choices }))
  )))
  .then((response) => {
    res.send(response);
  })
  .catch((err) => {
    // handle errors
  })
});

function getChoices(questionId) {
  const sql='SELECT id,text FROM `question_choices` where question_id=?';
  return new Promise((resolve, reject) => {
    con.query(sql,[questionId],(err,rows,fields)=>{
      if(err) reject(err);
      else resolve(rows);
    });
  });
}
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • Thanks for quick response..let me check it – Sreejith sreeji Nov 24 '18 at 06:26
  • when we use rows.map(({id})) then how can i get question object.i need choices inside question object. and item is not defined in map – Sreejith sreeji Nov 24 '18 at 06:34
  • Oh, you're right, the reassignment and mutation there is pretty confusing. See edit, better to get the `choices` and then spread the initial `item` into an object which has `choices` too – CertainPerformance Nov 24 '18 at 06:36
  • didint got what i want..my code is given below let object={}; Promise.all(rows.map((item)=>{ object=item; getChoices(item.id) .then((choices)=>{ object.choices=choices; response.push(object); }) })) .then(()=>{ res.send(response); }) .catch((err)=>{ console.log(err); }) – Sreejith sreeji Nov 24 '18 at 06:49
  • Try using the code in my answer instead. Your code isn't returning anything from the `.map` callback, nor have you declared a `response` variable. If my code isn't working, can you tell me what problems you're running into? – CertainPerformance Nov 24 '18 at 06:51
  • I have declared response array on the top of function .here I am looking for create a local object and save the each question into object and inside that object create choice array and add the choice result to choice array after that push the local object to response array.after all question complete return response array back to user – Sreejith sreeji Nov 24 '18 at 06:54
  • The code in your question (and in your comment) doesn't have `response` declared anywhere, which is why I was worried. My code attempts to accomplish exactly what you're looking for, if it's not working, can you explain what problems you're running into with it? – CertainPerformance Nov 24 '18 at 06:57
  • Do you mean `[null, null, null...]`, or what? That's quite odd, because there isn't anything that should produce `null` there, `.then((choices) => ({ ...item, choices }))` means that each item in the array should resolve to an *object* – CertainPerformance Nov 24 '18 at 07:01
  • That sound exceedingly strange, are you sure you're using the code in my answer? I'm quite sure there's *no* way it can resolve to `null` values currently – CertainPerformance Nov 24 '18 at 07:05
  • ok sir..thank you..it working.. for sake of my knowledge how can i do the same thing in my way.as above code i post..ho one pblm is there..it return same object 4 times – Sreejith sreeji Nov 24 '18 at 07:07
  • You're reassigning `object`, an *outer scoped variable*, each time, so by the time `getChoices` resolves, `object` refers to *only the final `item`*. You should declare `object` *inside* the `forEach` instead (the duplicate object problem is similar to https://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example ) – CertainPerformance Nov 24 '18 at 07:18
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/184170/discussion-between-sreejith-sreeji-and-certainperformance). – Sreejith sreeji Nov 24 '18 at 07:20