Node.js中forEach循环内MySQL查询无法返回数组传入EJS模板问题
这个问题我太熟了——核心原因是MySQL查询是异步操作,而forEach是同步执行的循环。你在forEach里发起查询,但循环会直接跑完,根本等不到异步查询完成,最后传给EJS的stock数据自然没有关联的product信息。
先给你拆解下问题:当你的forEach循环遍历每一条stock记录时,它会立刻发起MySQL查询,但这些查询都是异步的,会在后台执行。而外层的代码不会等这些查询完成,直接就继续往下走(比如可能你之后直接res.render了),这时候stock数组里还没添加上product数据,EJS模板自然拿不到。
下面给你几个可行的解决方案:
方案1:使用async/await + for...of循环(最直观易读)
把你的回调改成async函数,用for...of代替forEach,这样就能确保每一次查询完成后再进行下一次循环:
app.get("/admin/stock", async function(req, res) { try { // 先获取所有stock数据 const [stock] = await db.promise().query("SELECT * FROM stock"); // 用for...of遍历,确保异步操作顺序执行 for (let item of stock) { // 查询对应product,用参数化查询防止SQL注入 const [product] = await db.promise().query( "SELECT * FROM product WHERE productID = ?", [item.stock_productID] ); // 把product数据挂载到stock item上 item.product = product[0]; // query返回数组,取第一个匹配项 } // 所有数据准备好后,再传给EJS模板 res.render("admin/stock", { stock: stock }); } catch (err) { console.log("数据库查询出错:", err); res.status(500).send("服务器错误"); } });
注意这里用了db.promise(),如果你的MySQL库(比如mysql2)支持Promise形式就可以直接用;如果是旧版mysql库,可能需要用util.promisify把回调式的query转换成Promise形式。另外一定要用参数化查询(?占位符),别直接拼接SQL字符串,防止SQL注入!
方案2:使用Promise.all批量处理(性能更优)
如果你的stock数据很多,for...of是串行执行查询,速度会慢一些。这时候可以用Promise.all并行发起所有查询,效率更高:
app.get("/admin/stock", async function(req, res) { try { const [stock] = await db.promise().query("SELECT * FROM stock"); // 生成所有product查询的Promise数组 const productPromises = stock.map(item => db.promise().query( "SELECT * FROM product WHERE productID = ?", [item.stock_productID] ) ); // 等待所有查询完成 const productResults = await Promise.all(productPromises); // 把product数据对应到对应的stock item上 stock.forEach((item, index) => { item.product = productResults[index][0]; }); res.render("admin/stock", { stock: stock }); } catch (err) { console.log("数据库查询出错:", err); res.status(500).send("服务器错误"); } });
这个方法同时发起所有product查询,比串行执行快很多,适合数据量较大的场景。
方案3:用SQL JOIN一次性查询(最优解)
其实根本不需要嵌套查询,直接用SQL的JOIN把两张表关联起来,一次查询就能拿到所有需要的数据,性能最好,代码也最简洁:
app.get("/admin/stock", function(req, res) { // 用INNER JOIN关联stock和product表,一次性获取所有关联数据 const query = ` SELECT s.*, p.* FROM stock s INNER JOIN product p ON s.stock_productID = p.productID `; db.query(query, function (err, results) { if (err) { console.log("数据库查询出错:", err); return res.status(500).send("服务器错误"); } // 直接把结果传给EJS,每个结果里包含stock和product的所有字段 res.render("admin/stock", { stock: results }); }); });
这个方案省去了所有异步循环的麻烦,数据库层面直接关联数据,是最推荐的做法——不仅代码少,性能也比多次查询好太多。
最后提醒下:不管用哪种方案,都要注意错误处理,别忽略err的情况;另外永远用参数化查询避免SQL注入,不要直接拼接字符串!
内容的提问来源于stack exchange,提问作者SomeGenuineGuy

