You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 03:58:11