【问题标题】:For loop with Node js promise chaining带有 Node js 承诺链的 for 循环
【发布时间】:2019-08-17 18:31:07
【问题描述】:

我对 Node js 很陌生,异步编程对我来说似乎很难掌握。我正在使用 Promise-mysql 使流程同步,但我在 Promise 链中遇到了 for 循环的障碍

我有一个选择题模块。一个表存储所有 mcq 问题,另一个存储问题的所有相关选项。我使用第一个查询的输出作为第二个查询的输入,所以我承诺如下链接

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

我正在尝试创建一个看起来像这样的 javascript 对象数组

[{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}....
]

当我运行上述代码时,该对象会正确填充所有问题的选项,但在所有问题的所有 q_text 中都会重复相同的问题。

[ { 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}]
]

我觉得这与异步流程有关,因为在第二个查询之后打印任何内容之前,console.log 在第二个查询之后打印任何内容之前全部打印。任何见解将不胜感激

编辑:我添加了一个示例输出以便更好地理解。如输出所示,选项更改并存储在 for 循环中的 js 对象中,但所有对象的问题都更新为 for 循环中的最后一个问题

【问题讨论】:

  • var i 更改为 let i 因为 var 是函数作用域,因此 for 循环中的所有内容都将共享相同的 i 并且您在内部有异步调用循环,所以循环首先完成,然后事件循环将开始解析异步调用,因为循环已经完成,所以 i 的值将是数组的长度,将用于所有异步调用
  • 您能否添加一个错误的输出,以便更好地了解复制的数据是什么?
  • @ManuelSpigolon 添加了示例输出
  • @AZ_,我试过 let i 但没什么区别

标签: javascript node.js asynchronous promise


【解决方案1】:

node js 当前工作 asyncawait, still now use to async and await, 使用此参考网址:https://javascript.info/async-await

async 和 await 就像 Promise 一样工作,await is use to wait to execute script 例子

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

【讨论】:

    【解决方案2】:

    你的范围有问题

    这是重现您的问题的示例:

    ques 是一个在 for 循环中更新的全局变量,因此,当异步代码结束执行时,将读取具有最后一个 ques = result[i] 值的全局变量。

    '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))
    }
    

    但是,如果您只是像这样声明ques

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

    一切都会好起来的。

    最好使用constlet 而不是var,因为最后一个会创建一个危险的全局范围变量。


    关于您的评论:输出为空,因为此 for 循环是同步的,因此您以同步方式回复响应。

    如何处理此案例的示例如下:

    '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))
    }
    

    考虑到实现这一点的可能性有很多:

    • 使用for async iterator顺序运行查询
    • 通过只运行一个带有in 条件的查询而不是为每个q_id 运行一个查询来提高性能,并使用一些代码对结果进行分组来管理结果
    • 在示例中使用 promise 数组

    深入并选择最适合您需要的那个。

    重要提示:.catch 始终是承诺链!

    【讨论】:

    • 非常感谢@ManuelSpigolon,这对我帮助很大,完美的答案。我一直在努力解决这个问题。还有一个问题。如果我想对带有 mcqAll 对象的页面执行 res.render,我应该在什么时候编写它? res.render('mcqAllPage', {mcqAll}) 在 for 循环之后写入以某种方式不起作用并发送一个空数组。还是我错过了什么?
    猜你喜欢
    • 1970-01-01
    • 2016-08-20
    • 1970-01-01
    • 2017-12-10
    • 1970-01-01
    • 1970-01-01
    • 2015-12-18
    • 2013-06-17
    相关资源
    最近更新 更多