Node.js+Express从SQL Server取数渲染至EJS模板问题求助
解决Node.js+Express+Tedious获取SQL Server数据并渲染到EJS的问题
嘿,我看你遇到的问题很典型——异步代码没处理好,导致拿不到数据库返回的结果。别担心,我一步步帮你理清楚,修改代码让它正常工作:
问题分析
你的代码里有几个关键问题:
- 异步流程没处理:
executeStatement是异步执行SQL查询的,但你没通过回调、Promise或者async/await来获取它的结果,所以路由函数里根本拿不到column.value - 结果没保存:在
row事件里你访问了column.value,但没把它存到变量里,用完就丢了 - 连接关闭时机错误:你在Request的回调里直接关闭了连接,这会导致后续请求无法使用这个连接,而且可能在数据还没完全返回时就断开了
修改后的完整代码
var express = require('express'); var tediousExpress = require('express4-tedious'); var app = express(); var Connection = require('tedious').Connection; var Request = require('tedious').Request; app.set('view engine', 'ejs'); app.set('views', './views'); // 数据库配置(记得替换成你的真实凭据) var config = { server: '你的SQL Server地址', authentication: { type: 'default', options: { userName: '用户名', password: '密码' } }, options: { database: '数据库名', encrypt: true // 如果是Azure SQL需要开启,本地可能不需要,根据情况调整 } }; var connection = new Connection(config); // 监听连接成功事件 connection.on('connect', function(err) { if (err) { console.error('连接数据库失败:', err); } else { console.log('成功连接到SQL Server'); } }); app.use(function (req, res, next) { req.sql = tediousExpress(connection); next(); }); // 封装数据库查询为Promise,方便用async/await处理 function executeStatement() { return new Promise((resolve, reject) => { let resultData = []; // 用来保存查询结果 const request = new Request("SELECT * FROM table WHERE ArticleId = 24588 FOR JSON PATH", function(err, rowCount) { if (err) { console.error('查询出错:', err); reject(err); // 出错时拒绝Promise } else { console.log(`总共获取了 ${rowCount} 行数据`); // 注意:FOR JSON PATH返回的是JSON字符串,需要解析成对象 const parsedData = resultData.length ? JSON.parse(resultData[0]) : []; resolve(parsedData); // 成功时返回解析后的数组 } }); // 每一行数据触发的事件 request.on('row', function(columns) { columns.forEach(function(column) { if (column.value !== null) { resultData.push(column.value); // 把拿到的value存到数组里 } }); }); // 执行SQL请求 connection.execSql(request); }); } app.get('/products', async function (req, res) { try { // 等待数据库查询完成,拿到结果 const products = await executeStatement(); // 渲染EJS模板,把products数据传给前端 res.render('products', { products: products }); // 如果想先测试返回JSON,可以用这个:res.json({ products: products }); } catch (err) { console.error('路由处理出错:', err); res.status(500).send('服务器内部错误'); } }); // 启动服务器,监听端口 const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`服务器运行在 http://localhost:${PORT}`); });
关键修改点说明
用Promise封装异步查询:
把executeStatement改成返回Promise的函数,这样就能用async/await来处理异步操作,让代码像同步一样好理解,新手更容易上手。保存查询结果:
声明了resultData数组,在row事件里把每个column.value存进去,最后在Request的回调里解析JSON字符串(因为你用了FOR JSON PATH,返回的是JSON格式的字符串),再返回给路由。正确处理异步流程:
路由函数改成async函数,用await等待executeStatement的结果,拿到后再用res.render把数据传给EJS模板。调整连接关闭逻辑:
去掉了每次请求后关闭连接的代码,因为建立数据库连接开销很大,应该复用这个连接。如果需要关闭连接,可以在应用停止的时候(比如收到SIGINT信号)再关闭。
EJS模板示例(views/products.ejs)
假设你的模板是这样的,就可以直接用传过来的products数组:
<!DOCTYPE html> <html> <head> <title>产品列表</title> </head> <body> <h1>产品信息</h1> <% if (products.length > 0) { %> <ul> <% products.forEach(product => { %> <li>产品ID: <%= product.ArticleId %>,名称: <%= product.ProductName %></li> <!-- 根据你的实际字段调整 --> <% }) %> </ul> <% } else { %> <p>没有找到相关产品</p> <% } %> </body> </html>
额外提示
- 记得把
config里的数据库凭据替换成你自己的,还有SELECT语句里的表名和字段也要对应你的数据库结构。 - 如果不需要
FOR JSON PATH,可以去掉,直接返回行数据,那resultData里就是每一行的对象,不用再解析JSON了。 - 生产环境建议用连接池(Tedious的
ConnectionPool)来管理数据库连接,比单连接更稳定高效。
内容的提问来源于stack exchange,提问作者relu
相关产品推荐
相关产品推荐

