简体   繁体   中英

For loop with Node js promise chaining

I am very new to Node js and asynchronous programming seems difficult for me to grasp. I am using promise-mysql to make the flow synchronous but I have hit a road block with for loop inside of a chain of promise

I have a multiple choice question module. One table stores all the mcq questions and the other stores all the related choices for the questions. I am using the output of the first query as an input to the second query and so I did promise chaining as below

var mcqAll=[]
var sql_test_q_ans='select qId, q_text from questions'
    con.query(sql_test_q_ans)
    .then((result)=>{
      for(var i=0; i<result.length; i++)
      {
        ques=result[i]
        var sql_test_q_ops='SELECT op_text, op_id FROM mc_ops WHERE 
                             q_id='+result[i].q_id
        con.query(sql_test_q_ops)
        .then((resultOps)=>{
          mcqAll.push({i: ques, ops: resultOps})
          console.log(mcqAll)
         })
      }
    })

I am trying to create a javascript object array which would look something like this

[{q_text:'How many states in USA', q_ops:{1:25, 2:35, 3:45, 4:50}}
 {question2 and its options}
 {question3 and its options}....
]

When I run the above code the object populates all the question's options correctly but the same question is repeated in all the q_text for all questions.

[ { q_text: 'No of states in USA',
  [ {op_text: '25', mc_op_id: 113 },
    { op_text: '35', mc_op_id: 114 },
    { op_text: '45', mc_op_id: 115 },
    { op_text: '50', mc_op_id: 116}],
  { q_text: 'No of states in USA',
  [ {op_text: 'A', mc_op_id: 1 },
    { op_text: 'B', mc_op_id: 2 },
    { op_text: 'C', mc_op_id: 3 },
    { op_text: 'D', mc_op_id: 4}],
  { q_text: 'No of states in USA',
  [ {op_text: 'Yes', mc_op_id: 31 },
    { op_text: 'No', mc_op_id: 32 },
    { op_text: 'No sure', mc_op_id: 33 },
    { op_text: 'Might be', mc_op_id: 34}]
]

I feel like it has something to do with asynchronous flow since console.log before the second query gets printed in all before printing anything after the second query. Any insight would be appreciated

Edit: I added a sample output for better understanding. As seen in the output, the options change and get stored in the js object in the for loop but the question is updated for all the objects to the last question in the for loop

node js current working async and await , still now use to async and await , use this reference url: https://javascript.info/async-await

async and await is work as promise, await is use to wait to execute script example

let mcqAll=[]
let sql_test_q_ans='select qId, q_text from questions'
async function showAvatar() {
   let result = await con.query(sql_test_q_ans);
   if(result.length > 0){
      array.forEach((async function (item, index, result) {
        let q =  result[index];
        let sql_test_q_ops='SELECT op_text, op_id FROM mc_ops WHERE 
                             q_id='+result[index].q_id  
        let executeQuery = await con.query(sql_test_q_ops);    
        if(executeQuery.affectedRows > 0){
           mcqAll.push({index: q, ops: executeQuery})
           console.log(mcqAll);
        }
      });
   }
 }

You have a scope problem here

This is an example to reproduce your problem:

ques is a global variable that is updated in the for-loop so, when the async code ends the execution will read the global variable with the last ques = result[i] value.

'use strict'
const result = ['a', 'b', 'c']
const mcqAll = []
var ques
for (var i = 0; i < result.length; i++) {
  ques = result[i]
  var sql_test_q_ops = 'SELECT op_text, op_id FROM mc_ops WHERE q_id = ' + result[i].q_id
  query(sql_test_q_ops)
    .then(() => {
      mcqAll.push({ i: ques })
      console.log(mcqAll)
    })
}

function query() {
  return new Promise(resolve => setTimeout(resolve, 100))
}

But, if you simply declare the ques like this:

for (var i = 0; i < result.length; i++) {
  const ques = result[i]
  const sql_test_q_op...

all will work.

It is a good practice to use const or let instead of var because the last one creates a global scoped variable that is dangerous.


Regarding your comment: the output is empty because this for-loop is sync, so you reply in sync way to the response.

An example on how to manage this case could be like this:

'use strict'
const result = ['a', 'b', 'c']
const mcqAll = []

const promiseArray = result.map(ques => {
  const sql_test_q_ops = 'SELECT op_text, op_id FROM mc_ops WHERE q_id = ' + ques.q_id
  return query(sql_test_q_ops)
    .then(() => { mcqAll.push({ i: ques }) })
})

// Wait for all the query to complete before rendering the results
Promise.all(promiseArray)
  .then(() => {
    console.log({ mcqAll });
    res.render('mcqAllPage', { mcqAll })
  })
  .catch(err => res.send(500)) // this is an example

function query() {
  return new Promise(resolve => setTimeout(resolve, 100))
}

Consider that there are many possibilities to implement this:

  • use for async iterator to run query sequentially
  • improve performance by run only one query with a in condition instead of a query for each q_id and manage the result with some code to group the results
  • using the promise array as in the example

Go deeper and choose the one that fits best for your need.

Important: .catch always the promise chain!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM