Node.js报错Cannot set headers 重复设置响应头问题排查
Cannot set headers after they are sent to the client 报错定位 你的代码存在3处直接/间接导致该报错的问题,按影响优先级排序:
1. 链式重复调用响应方法
车辆占用校验分支存在明显语法错误:
// 错误写法 return response.send(403).send({ status: 403, message: `vehicle with id ${vehicleId} is not available for rental` })
这里连续调用了两次send():第一次send(403)已经将响应发送到客户端,第二次链式调用send()会尝试再次修改响应头,直接触发报错。设置HTTP状态码应该用status()方法,而非send()。
2. 异步调用模式混用,await完全失效
node-postgres的pool.query方法支持两种互不兼容的异步模式:
- 传入回调函数时:方法不返回Promise,外层的
await不会等待SQL执行完成,代码会直接往下运行 - 不传回调函数时:方法返回Promise,可通过
await接收执行结果
你给所有SQL查询都同时加了await和回调函数,实际执行逻辑完全偏离预期: - 4条SQL语句会几乎同时发起执行,不会按你写的顺序等待上一条执行完再跑下一条
- 只要任意一个前置校验分支触发了响应发送,后续SQL执行完成后,其回调里的
response.send会再次尝试发送响应,直接触发报错 - 插入行程的SQL执行时,
driver、vehicle两个变量大概率还是undefined,会写入脏数据
3. 回调内抛出的异常无法被捕获
你在所有SQL回调里写的throw语句,无法被外层async函数捕获,会直接触发进程未捕获异常,不会按预期返回错误响应。
修正后参考实现
统一用Promise+async/await模式,去掉回调,加上事务保证数据一致性:
async function createTrip(request, response) { const { vehicleId, driverId, startedAt, expectedReturn } = request.body let driver, vehicle const poolClient = await pool.connect() try { await poolClient.query('BEGIN') // 车辆校验 const vehicleRes = await poolClient.query( 'SELECT * FROM Vehicle WHERE id = $1', [vehicleId] ) if (vehicleRes.rowCount === 0) { await poolClient.query('ROLLBACK') return response.status(404).send({ status: 404, message: `vehicle with id ${vehicleId} does not exist.` }) } if (vehicleRes.rows[0].in_use) { await poolClient.query('ROLLBACK') return response.status(403).send({ status: 403, message: `vehicle with id ${vehicleId} is not available for rental` }) } vehicle = vehicleRes.rows[0] // 司机校验 const driverRes = await poolClient.query( 'SELECT * FROM Driver WHERE id = $1', [driverId] ) if (driverRes.rowCount === 0) { await poolClient.query('ROLLBACK') return response.status(404).send({ status: 404, message: `driver with id ${driverId} does not exist` }) } driver = driverRes.rows[0] // 创建行程 const tripRes = await poolClient.query( 'INSERT INTO Trip (status,started_at, expected_return, driver, vehicle) VALUES ($1,$2,$3,$4,$5) RETURNING *', ['active', startedAt, expectedReturn, driverId, vehicleId] ) // 更新车辆状态 await poolClient.query( 'UPDATE Vehicle SET in_use = TRUE WHERE id = $1', [vehicleId] ) await poolClient.query('COMMIT') return response.status(200).send({ id: tripRes.rows[0].id, status: tripRes.rows[0].status, startedAt, expectedReturn, driver, vehicle }) } catch (error) { await poolClient.query('ROLLBACK') return response.status(500).send({ status: 500, message: `createTrip failed: ${error.message}` }) } finally { poolClient.release() } }
内容的提问来源于stack exchange,提问作者Eli Wennberg Smith
相关产品推荐
相关产品推荐

