即使在 'return' 之后循环也不会终止

While loop not terminating even after the 'return'

我正在使用 while 循环遍历数组并尝试使用 return 终止循环(我尝试过 foreach 之前不会终止 return 但 while 循环应该使用 return).

终止

谁能帮我看看为什么会这样?

这是我的代码:

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++;
    }
  })

输出: 对于长度为 2 的数组:

index 0

returned

index 1

returned

(它也会抛出错误,因为它发送了两次响应!

简而言之:return 语句适用于它所在函数的作用域。并且您的循环在外部作用域中。

可能的解决方案

您可以在与 while 循环相同的范围内定义另一个变量,在每次迭代中检查它,当您希望内部范围结束循环时,您不仅 return 而且也设置那个变量。

类似于:

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

和:

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

还有任何其他 return 应该停止循环的地方。

说明

你的情况:

  • 您调用 return 的作用域是作为参数传递给 db.query(...).
  • 回调 函数
  • db.query(...)本身在传递给new Promise(...)
  • 回调函数的范围内
  • ... 与您的 while 循环
  • 在同一范围内

因此,当您像现在这样调用 return 时,只会结束内部回调的执行。

您的 return 语句不是 req.on 回调中 while 循环的一部分,而是 dq.query 回调函数的一部分。所以它们与循环无关。

一种方法是 promisify db.query 函数。检查您的数据库 API 的文档,因为此方法可能已经有 promise-returning 替代方法。但如果没有,您可以改用这个通用函数:

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

现在您可以将那些 return 语句直接放入循环中,它们将退出 db.req 回调:

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!",
    });
  }
});