Node.js Express注册接口抛出ERR_HTTP_HEADERS_SENT错误怎么办
问题根因
这个报错的核心是没有正确处理Node.js中数据库查询的异步执行逻辑,具体的执行链路错位如下:
connection.query是异步非阻塞方法,调用后不会等待SQL执行完成、回调执行结束,就会直接向下执行后续的同步代码。- 你当前的代码在发起查重的SELECT查询后,根本没等查重结果返回,就立刻执行了密码哈希、INSERT插入新用户、最后直接调用
res.json("Added user")给客户端返回成功响应。 - 等几十毫秒后,之前发起的查重查询拿到数据库结果,发现存在重复的邮箱/用户名,就会进入回调逻辑再次调用
res.json()返回重复提示——这时候第一次的响应头、响应内容早就已经发给客户端了,自然就触发了Cannot set headers after they are sent to the client的报错。 - 你之前在查重回调里加的
return,只能中断回调函数内部的执行,完全管不到外层已经跑完全程、早就返回了成功响应的主流程,所以加了return也解决不了问题。
修复方案
要解决这个问题,核心是把所有依赖异步操作结果的逻辑,都放到对应的异步回调里,严格按顺序执行流程,杜绝跳步:
- 先执行查重查询,必须等查重结果返回、确认没有重复账号,才能继续后续的密码哈希、用户插入操作,不能在外面同步执行后面的逻辑。
- 所有响应返回的分支,都要放在对应的异步回调内部,保证整个流程里不管走哪个分支,都只会调用一次res响应方法,不会重复发送响应。
- 修正原有代码的逻辑缺陷:
- 原来的查重逻辑只判断结果集的第一条记录,要是同时存在同邮箱、同用户名的两条重复数据,会误判重复类型,需要遍历所有匹配结果做判断。
- 原来直接拼接SQL字符串的写法有SQL注入风险,改成参数化占位符写法。
- 去掉数据库回调里直接
throw err的写法,把数据库异常转换成友好的接口响应,避免程序崩溃。
修复后的可运行代码
const connection = require("../../db"); const bcrypt = require("bcrypt"); const register = (req, res) => { const { user_name, password, phone, email } = req.body; const role_id = 0; const dateObj = new Date(); const month = dateObj.getUTCMonth() + 1; const day = dateObj.getUTCDate(); const year = dateObj.getUTCFullYear(); const created_at = `${year}/${month}/${day}`; const is_disabled = 0 // 1. 先执行账号查重,所有后续逻辑放在回调内等待结果 const checkSql = `SELECT * from users WHERE email = ? OR user_name = ?`; connection.query(checkSql, [email, user_name], (err, checkResult) => { // 数据库异常直接返回错误,不继续执行后续逻辑 if (err) return res.status(500).json({ message: "数据库查询失败" }); // 存在重复账号的判断 if (checkResult.length > 0) { const isEmailExist = checkResult.some(item => item.email === email); const isUserNameExist = checkResult.some(item => item.user_name === user_name); if (isEmailExist) return res.json({ message: "Email is already used." }); if (isUserNameExist) return res.json({ message: "User name is already used." }); } // 2. 查重通过后再计算密码哈希 const hashPassword = bcrypt.hashSync(password, Number(process.env.SALT)); // 3. 执行新用户插入操作 const insertSql = `INSERT INTO users (email,phone,password,user_name,role_id,created_at,is_disabled) VALUES(?,?,?,?,?,?,?)`; connection.query(insertSql, [email, phone, hashPassword, user_name, role_id, created_at, is_disabled], (err) => { if (err) return res.status(500).json({ message: "用户注册失败" }); // 所有异步操作执行完成后,只返回一次成功响应 return res.json({ message: "Added user" }); }) }); } module.exports = register
补充说明:后续如果觉得嵌套回调太冗余,可以把数据库操作封装成Promise,配合async/await写更扁平的异步流程,逻辑可读性会更好。
内容的提问来源于stack exchange,提问作者Alaa Khalila
相关产品推荐
相关产品推荐

