Node.js 报错 "Can't set headers after they are sent" 怎么解决
该错误表示响应已发送后又试图修改 Header。三大根因:重复调用 res.send()、异步回调里未 return、中间件顺序错误。附典型错误代码对照与排查方法。
一句话回答:这个错误表示 HTTP 响应已经发给客户端,代码又试图设置 Header(setHeader/writeHead/send 等)。HTTP 协议里 Header 必须在响应体之前发送,响应一旦发出就无法修改。修复核心是保证每个请求只发送一次响应**,并在发送后立即 return 终止后续逻辑。**
三大根因与修法
1. 重复发送响应
// ❌ 错误:send 两次
app.get('/', (req, res) => {
res.send('第一次');
res.send('第二次'); // 触发错误
});
// ✅ 正确:只发一次
app.get('/', (req, res) => {
res.send('唯一响应');
});
2. 条件分支未终止,继续发送响应
// ❌ 错误:异步完成前函数已走完,或条件分支里两处都会执行
app.get('/', (req, res) => {
if (!req.query.name) {
res.status(400).send('缺少 name'); // 这里没有 return!
}
res.send('Hello ' + req.query.name); // 照样执行到 → 二次发送
});
// ✅ 正确:每个发送分支都 return
app.get('/', (req, res) => {
if (!req.query.name) {
return res.status(400).send('缺少 name');
}
return res.send('Hello ' + req.query.name);
});
3. 中间件顺序错误
修改 Header 的中间件必须放在发送响应的路由之前;如果某个中间件已经 next() 又自己发了响应,后面的路由再发就会冲突。排查时检查每个中间件是否"要么发响应,要么 next(),二选一"。
防御性写法
以下是路由/中间件片段,fetchData 由应用实现。Express 4 的异步错误应显式传给 next;Express 5 会处理返回的 Promise 拒绝,显式写法也便于统一逻辑。
app.get('/', async (req, res, next) => {
try {
const data = await fetchData();
return res.json(data);
} catch (err) {
return next(err);
}
});
// 放在路由之后;已发出响应头时交给 Express 默认错误处理器
app.use((err, req, res, next) => {
if (res.headersSent) return next(err);
return res.status(500).json({ error: 'Internal server error' });
});
return 仅结束当前函数,不会取消已经排队的回调或并发任务。应确保每个请求只有一个响应写入者;headersSent 也不等于响应体已经结束。
快速定位技巧
- 错误堆栈指向的是第二次发送的位置,真正的"第一次发送"在其上方——向上找同一路由里的所有
send/end/json/redirect/writeHead - 全局搜路由里的
res.send,确认每个分支都有return - 团队规范:所有发送响应的语句统一写成
return res.xxx(...),减少分支继续执行风险,但还须检查异步回调与并发响应写入
常见问题(FAQ)
Q:错误信息说 "headers",但我没调用过 setHeader?
A:res.send()、res.json()、res.redirect() 内部都会设置并发送 Header。报错的本质是"响应头已发送后又被修改",流式响应此时可能尚未结束,不一定是字面意义的 setHeader 调用。
Q:async 函数里更容易踩这个坑吗?
A:是的。await 之后的代码与 catch 分支容易形成"两处都发送"的路径。把错误传给 next(err),由末尾错误处理中间件检查 headersSent;不要在已开始的响应上再次发送 500。
Q:生产环境这个错误会让进程崩溃吗?
A:在 Express 中它作为异常抛出,未捕获时 Node 进程可能退出(取决于 Node 版本与错误处理)。这也是为什么必须修:它不只是日志噪音。
核查依据
本文依据官方文档核对,示例未在实际业务环境运行;上线前请按部署版本、权限与数据范围验证。