Mongoose预钩子中Station.updateMany报非函数错误?相似逻辑运行不一致
报错核心根因
这是典型的CommonJS模块循环依赖导致的常量引用赋值错位,和updateMany方法本身、模型配置没有任何关系。
你的两个模型文件存在互相require的循环依赖:
Customer.js顶层导入Station模型Station.js顶层导入Customer模型
Node.js处理循环依赖时的加载逻辑,会直接返回对方模块当前已经加载完成的内容,不会等对方全部代码执行完毕。一旦对方的导出对象被赋值给顶层的const常量,哪怕后续对方模块加载完成、导出了完整内容,这个常量持有的残缺引用也不会自动更新。
结合你遇到的“Station的预钩子正常、Customer的预钩子报错”的现象,对应的实际加载时序如下:
- 项目入口首先加载
Station.js,触发Station.js第一行的require('./Customer') - 进入
Customer.js的加载流程,第一行执行require('./Station')——此时Station.js还停在第一步的加载流程中,没有执行到定义schema、注册模型、导出完整Station模型的步骤,因此Node直接返回Station.js当前的空exports对象 Customer.js将这个空的Station对象赋值给自己顶层的const Station,这个赋值是永久生效的,不会随后续Station模块加载完成而更新Customer.js继续执行后续逻辑:定义customerSchema、注册save预钩子(钩子闭包捕获的Station就是刚才拿到的空对象),最后导出完整的Customer模型- 回到
Station.js的加载流程,将刚才拿到的完整Customer模型赋值给自己顶层的const Customer Station.js继续执行后续逻辑:定义stationSchema、注册save预钩子(钩子闭包捕获的Customer是完整的已注册模型),最后导出完整的Station模型
最终就会出现:
- Station的save预钩子里调用
Customer.updateMany正常,因为闭包拿到的Customer是完整模型 - Customer的save预钩子里调用
Station.updateMany报错,因为闭包拿到的Station是加载初期的空对象,根本不存在updateMany方法 - Resolver层的关联更新逻辑正常,因为Resolver代码执行时,所有模块已经完全加载完成,require拿到的都是缓存里完整的模型对象
解决方案
所有方案的核心目标都是消除循环依赖、保证钩子运行时拿到的是完整的模型引用:
- 去掉两个模型文件之间的顶层互相require,不要在模块导入阶段硬依赖对方模型
- 在save预钩子的执行逻辑内部,从mongoose的全局模型注册表中动态获取已注册的模型引用
示例修改后的Customer.js:
const mongoose = require('mongoose') const customerSchema = mongoose.Schema({ stations: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Station' }] }) customerSchema.pre('save', async function() { // 钩子执行时所有模型已经完成注册,从注册表拿完整引用 const Station = mongoose.model('Station') await Station.updateMany( { _id: { $in: this.stations } }, { $addToSet: { customers: this._id } } ) }) module.exports = mongoose.model('Customer', customerSchema)
示例修改后的Station.js:
const mongoose = require('mongoose') const uniqueValidator = require('mongoose-unique-validator') const stationSchema = new mongoose.Schema({ customers: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Customer' }] }) stationSchema.plugin(uniqueValidator) stationSchema.pre('save', async function() { // 钩子执行时从注册表拿模型,避开顶层循环依赖 const Customer = mongoose.model('Customer') await Customer.updateMany( { _id: { $in: this.customers } }, { $addToSet: { stations: this._id } } ) }) module.exports = mongoose.model('Station', stationSchema)
补充排查验证方式
你可以在Customer的save预钩子里加一行日志,直接打印当前闭包里拿到的Station是什么:
customerSchema.pre('save', async function() { console.log('Station引用内容:', Station) // 正常应该是带有updateMany等模型方法的对象,这里大概率会打印出空对象或者不完整的导出内容 })
输出结果可以直接验证循环依赖的时序问题。
内容的提问来源于stack exchange,提问作者amrit regmi
相关产品推荐
相关产品推荐

