Node.js中多次调用res.send()无法向客户端发消息的问题
res.send()仅能调用一次,后续调用无效 我正在学习Node.js,参考教程构建REST API。目前遇到一个棘手的问题:在user_routes.js文件里尝试通过Express多次向客户端发送消息,但第一次调用res.send()后,后面的所有调用都不生效。我明明没关闭连接,却没法多次向请求写入内容。
以下是我的代码:
user_routes.js
module.exports = function(app, db) { app.post('/user', function (req,res) { res.send("User Request Recieved via POST"); // 若用户不存在则添加到数据库 const firstName = req.body.firstName; const lastName = req.body.lastName; const email = req.body.email; const password = req.body.password; const user = { firstName: firstName, lastName : lastName, email : email, password : password }; if (db.collection('users').find({'email':email}).count() == 0) { res.send('Unique Email'); db.collection('users').insert(user, (err, result) => { if (err) { console.log("error"); } else { console.log(result.ops[0]) } }); } else { res.send("Email already in use") } }) };
server.js
const express = require('express'); const MongoClient = require('mongodb').MongoClient; const bodyParser = require('body-parser'); const app = express(); const port = 6969; const db = require('./config/db') // 使用body-parser解析URL编码的数据 app.use(bodyParser.urlencoded({ extended: true })); MongoClient.connect(db.url).then( function(db) { require('./app/routes')(app, db); app.listen(port, () => { console.log('We are live on ' + port); }); }).catch (function (err) { console.log(err); }); module.exports = app;
我没关闭连接,为什么只能向客户端发送一次消息?
这是Express的响应循环机制导致的问题,和数据库连接没关系,我给你拆解原因和解决方案:
核心原因:res.send()会直接结束响应循环
在Express里,res.send()可不只是发数据这么简单——它还会自动帮你设置响应头(比如Content-Type),并且标记当前HTTP请求已处理完成,直接结束响应循环。
一旦调用过res.send(),Express就认定这个请求已经搞定了,后续再试图给同一个响应对象发数据(比如再次调用res.send()、res.json())都会被忽略,甚至终端会抛出警告(你可以看看Node.js控制台,应该能看到相关提示)。
你的代码里先调用了res.send("User Request Recieved via POST"),这已经把响应循环关了,后面的res.send('Unique Email')或者res.send("Email already in use")自然不会生效。
额外坑点:你的数据库查询逻辑是错的
另外,你代码里的db.collection('users').find({'email':email}).count() == 0有问题——MongoDB的count()是异步方法,返回的是Promise对象,你直接用同步方式判断等于0,结果永远是false(因为你在拿Promise和0比),这会导致整个逻辑走偏。
解决方案
方案1:整合所有响应内容,一次发送
如果只是想给客户端返回多个状态信息,建议把内容整合成字符串、对象或数组,用一次res.send()发送:
module.exports = function(app, db) { app.post('/user', function (req,res) { const firstName = req.body.firstName; const lastName = req.body.lastName; const email = req.body.email; const password = req.body.password; const user = { firstName, lastName, email, password }; // 初始化响应消息 let responseMsg = "User Request Recieved via POST\n"; // 用异步方式处理数据库查询 db.collection('users').find({'email':email}).count() .then(count => { if (count === 0) { responseMsg += 'Unique Email'; return db.collection('users').insert(user); } else { responseMsg += "Email already in use"; return Promise.resolve(); } }) .then(result => { if (result) { console.log(result.ops[0]); responseMsg += "\nUser added successfully"; } // 最后一次发送完整响应 res.send(responseMsg); }) .catch(err => { console.log("error", err); res.status(500).send("Server Error"); }); }) };
方案2:用res.write()分批发送,最后用res.end()结束
如果确实需要分批给客户端发数据,可以用res.write()写入内容,最后调用res.end()来结束响应循环:
module.exports = function(app, db) { app.post('/user', function (req,res) { // 手动设置响应类型,避免乱码 res.setHeader('Content-Type', 'text/plain'); // 先写入第一条消息,不结束响应 res.write("User Request Recieved via POST\n"); const firstName = req.body.firstName; const lastName = req.body.lastName; const email = req.body.email; const password = req.body.password; const user = { firstName, lastName, email, password }; db.collection('users').find({'email':email}).count() .then(count => { if (count === 0) { res.write('Unique Email\n'); return db.collection('users').insert(user); } else { res.write("Email already in use\n"); return Promise.resolve(); } }) .then(result => { if (result) { console.log(result.ops[0]); res.write("User added successfully\n"); } // 结束响应 res.end(); }) .catch(err => { console.log("error", err); res.write("Server Error\n"); res.end(); }); }) };
注意:res.write()不会自动设置Content-Type,需要手动调用res.setHeader()指定,否则可能出现乱码。
内容的提问来源于stack exchange,提问作者user2055654

