Home > Mobile >  While loop not terminating even after the 'return'
While loop not terminating even after the 'return'

Time:05-14

I am using the while loop to iterate through an array and trying to terminate the loop using the return (I tried foreach before which doesn't terminate for return but the while loop should terminate with the use of return).

Can someone help me why this is happening?

Here is my code:

req.on("data", async data => {
    let fetchedData = JSON.parse(data.toString())
    let index=0
    while(index<fetchedData.length) {
      const invoice = fetchedData[index]
    // fetchedData.forEach((invoice, index) => {
      console.log('index',index)
      await new Promise((resolve, reject) => {
        db.query(
          `SELECT * FROM payments WHERE invoice_id = '${invoice.id}' and status = 1`,
          (err, results) => {
            if (err) {
              resolve()
              return res.json({
                status: "error",
                message: "something went wrong!",
              })
            } else {
              if (results.length > 0) {
                resolve()
                console.log('returned')
                return res.json({
                  status: "error",
                  message: `Payment is already done for invoice number ${invoice.invoiceNumber} ! Please delete the payment first.`,
                })
              } else if (index == fetchedData.length - 1) {
                for(let i =0; i<fetchedData.length; i  ){
                  db.query(`UPDATE invoices SET status = 0 WHERE id = '${fetchedData[i].id}'`, (err, results) => {
                    if (err) {
                      resolve()
                      return res.json({
                        status: "error",
                        message: "something went wrong!",
                      })
                    } else{
                      deletedInvoices.push(fetchedData[i].id)
                      if(i == fetchedData.length - 1){
                        console.log('deleted')
                        return res.json({
                          status: "success",
                          message: "invoice deleted",
                          deletedInvoices: deletedInvoices
                        })
                      }
                      resolve()
                    }
                  })
                }
              }
            }
          }
        )
      })
      index  ;
    }
  })

output: for an array with the length of 2:

index 0

returned

index 1

returned

(it also throws an error because it is sending a response two times!

CodePudding user response:

In short: the return statement applies to the scope of the function that it is in. And your loop is in an outer scope.

Possible solution

You could define another variable in the same scope as you while loop, check it in each iteration, and when you want an inner scope to end the loop, you not only return but also set that variable.

Something like:

// ...
let index=0
let shouldLoopContinue = true
while(shouldLoopContinue && index<fetchedData.length) {
// ...

and:

// ...
if (err) {
  resolve()
  shouldLoopContinue = false
  return res.json({
    status: "error",
    message: "something went wrong!",
  })
}
// ...

And also in any other place that returns and should stop the loop.

Explanation

In your case:

  • the scope you call return from is the callback function passed as a parameter to db.query(...).
  • db.query(...) itself is in the scope of the callback function passed to new Promise(...)
  • ... which is in the same scope as your while loop

So when you call return the way you do now, you only end the execution of that inner callback.

CodePudding user response:

Your return statements are not part of the while loop that is in the req.on callback, but are part of dq.query callback functions. So they don't relate to the loop.

An approach is to promisify the db.query function. Check the documentation of your database API, as there might already be a promise-returning alternative for this method. But if not, you can use this generic function instead:

const asyncQuery = (db, sql) =>
    new Promise((resolve, reject) =>
        db.query(sql, (err, results) =>
            err ? reject(err) : resolve(results)
        )
    );

Now you can put those return statements directly in your loop, and they will exit the db.req callback:

req.on("data", async data => {
  try {
    const fetchedData = JSON.parse(data.toString())
    for (const {id, invoiceNumber} of fetchedData) { // Simpler loop
      const results = await asyncQuery(db, `SELECT * FROM payments WHERE invoice_id = '${id}' and status = 1`)
      if (results.length > 0) {
        // Now we are in the req.on callback, so this will exit it:
        return res.json({
          status: "error",
          message: `Payment is already done for invoice number ${invoiceNumber} ! Please delete the payment first.`,
        });
      }
    }
    for (const {id} of fetchedData) {
      await asyncQuery(db, `UPDATE invoices SET status = 0 WHERE id = '${id}'`);
    }
    res.json({
      status: "success",
      message: "invoice deleted",
      deletedInvoices: fetchedData.map(({id}) => id)
    });
  } catch (err) {
    res.json({
      status: "error",
      message: "something went wrong!",
    });
  }
});
  • Related