将Promise传入pg-promise批量事务时出现未捕获异常
核心问题拆解
1. Promise提前执行,脱离事务上下文
你封装的batchQuery函数接收的queryArray是提前执行完毕的Promise数组——调用addToColumn(...)时,会立即用全局db对象发送SQL请求,完全和事务的t上下文无关。
当数据库出错时,这些Promise的异常要么被addToColumn内部的catch处理(如果handleQueryErr没重新抛出,Promise会被标记为resolved),要么直接逃逸出事务的错误处理范围,导致服务器因未捕获异常崩溃。
对比手动编写的事务代码,你是在db.tx(t => {})的回调内部调用addToColumn,此时即使没传t,整个事务的Promise异常会被外层的catch捕获;但封装版的Promise在事务启动前就已经执行,异常无法被事务的错误处理覆盖。
2. 错误处理层级冲突
addToColumn内部的.catch(err => handleQueryErr(err))会拦截查询异常,如果handleQueryErr没有重新抛出错误,该Promise会被视为成功状态,事务不会触发回滚逻辑。
3. pg-promise batch的正确用法
t.batch()要求传入的Promise必须是在事务上下文t下创建的,这样所有查询才属于同一个事务组,才能实现“任一失败则全部回滚”的效果。提前创建的Promise(用全局db执行)不在事务范围内,自然无法触发回滚。
各阶段代码问题解析
错误封装的batchQuery
const batchQuery = (queryArray) => { return db.tx(t => { return t.batch(queryArray); // queryArray的Promise已用全局db执行,和事务无关 }).catch(err => handleTransactionErr(err)); };
这里的queryArray在传入batchQuery前就已经执行,事务的t对象根本没参与查询过程,数据库错误无法被事务的catch捕获。
改用回调后的问题
你尝试传入回调函数生成Promise数组,但addToColumn依然用全局db执行,每个查询都是独立事务,不属于同一个事务上下文,所以单个查询失败不会触发其他查询的回滚。
最终解决方法的合理性
给addToColumn添加可选的事务上下文参数t,在事务内部调用时传入t,确保所有查询都绑定到当前事务:
const addToColumn = (tableName, columnName, entryId, amountToAdd, t) => { let context = t || db; return context.one('UPDATE ${table:name} SET ${column:name} = ${column:name} + ${amount:csv} WHERE id = ${id:csv} RETURNING *', { table: tableName, column: columnName, amount: amountToAdd, id: entryId }).catch(err => handleQueryErr(err)); };
此时所有查询都在同一个事务t下执行,任一失败都会触发整个事务回滚,错误也能被事务的catch正确捕获,不会导致服务器崩溃。
关键注意事项
- 查询函数不要私自拦截异常:如果
handleQueryErr没有throw err,事务会认为该查询成功,无法触发回滚。建议将错误处理交给上层事务的catch,或在handleQueryErr中重新抛出错误。 - 事务内必须使用
t上下文:所有属于同一事务的操作,都要通过事务对象t执行,不能用全局db,否则操作不在事务范围内,无法实现回滚。 - Promise要在事务回调内创建:事务相关的Promise必须在
db.tx(t => {})的回调函数内部生成,才能绑定到事务上下文。
内容的提问来源于stack exchange,提问作者Brenden

