Mongoose v8.7.3中findOneAnd系列方法的post钩子无法触发
Mongoose v8.7.3中findOneAnd系列方法的Post钩子未触发问题排查与解决
我在使用Mongoose v8.7.3时遇到一个问题——调用findOneAndUpdate()或findOneAndDelete()时,pre钩子能正常执行,但post钩子完全没触发。日志只打印pre钩子的输出,post钩子的日志毫无动静,调试时还发现query对象存在异常。
相关代码片段
通用更新函数
const updateOne = (Model) => catchAsync(async (req, res, next) => { const doc = await Model.findByIdAndUpdate(req.params.id, req.body, {new: true, runValidators: true}); if (!doc) return next(new AppError(`No document is found with ID: ${req.params.id}`, 404)); res.status(200).json({ status: "success", data: doc }); });
Pre钩子(正常执行)
reviewSchema.pre(/^findOneAnd/, async function (next) { console.log("------------------------------------------------------------------------------------12"); this.r = await this.model.findOne(this.getQuery()); next(); });
Post钩子(未触发)
reviewSchema.post(/^findOneAnd/, async function () { console.log("-------------------------------------"); await (this.r.constructor.calculateAverageRating(this.r.tour)); });
问题原因
在Mongoose 8.x版本中,findOneAnd*属于查询中间件,pre和post钩子的上下文(this)存在差异:pre钩子的this指向query对象,但post钩子的this绑定逻辑在部分场景下会出现异常,导致pre钩子中挂载的this.r无法被post钩子访问,甚至直接导致post钩子不执行。另外,findByIdAndUpdate是findOneAndUpdate的语法糖,同样受此规则约束。
解决方案
方案1:规范中间件上下文传递
修改pre钩子将数据挂载到query的自定义属性上,post钩子通过query对象获取该数据,同时确保post钩子接收正确的参数:
// 修改pre钩子,把原始文档挂载到query的自定义属性 reviewSchema.pre(/^findOneAnd/, async function (next) { console.log("------------------------------------------------------------------------------------12"); this._originalDoc = await this.model.findOne(this.getQuery()); next(); }); // 修改post钩子,从query对象获取pre钩子保存的数据 reviewSchema.post(/^findOneAnd/, async function (updatedDoc) { console.log("-------------------------------------"); const originalDoc = this._originalDoc; if (originalDoc) { await originalDoc.constructor.calculateAverageRating(originalDoc.tour); } });
方案2:改用文档实例方法触发文档中间件
如果业务允许,先查询文档实例,再调用实例的updateOne/deleteOne方法,这样会触发文档中间件而非查询中间件,避免上下文绑定问题:
// 调整updateOne函数逻辑,先查再更新 const updateOne = (Model) => catchAsync(async (req, res, next) => { const doc = await Model.findById(req.params.id); if (!doc) return next(new AppError(`No document is found with ID: ${req.params.id}`, 404)); // 使用文档实例的updateOne方法,触发文档中间件 await doc.updateOne(req.body, {runValidators: true}); // 重新查询获取更新后的文档 const updatedDoc = await Model.findById(req.params.id); res.status(200).json({ status: "success", data: updatedDoc }); });
方案3:验证版本兼容性
如果上述方案无效,可能是Mongoose 8.7.3的特定bug,可以尝试降级到8.6.x版本或升级到最新8.x版本,验证是否能解决问题。
内容的提问来源于stack exchange,提问作者zen
相关产品推荐
相关产品推荐

