Mongoose虚拟属性中如何使用async/await及解决Promise pending问题
问题根源
你碰到的pending Promise问题,本质是两个写法层面的硬错误,叠加Mongoose本身的设计限制导致的:
- Mongoose的虚拟属性setter是同步执行逻辑,从框架设计上就不支持传入async异步函数。你把setter定义为async后,Mongoose不会等待内部的异步操作完成,直接把
returnSecurePassword返回的未决Promise赋值给encry_password字段,自然永远拿不到加密结果。 - 你写的
returnSecurePassword方法本身逻辑有缺陷:调用回调形式的bcrypt.hash时,回调内部的return值只会返回给回调函数自身,根本无法传递到外层async函数的返回值;同时直接await带回调的bcrypt调用,拿到的是undefined,本身就没有有效返回值。
正确实现方式
放弃在虚拟setter中做异步加密操作,改用Mongoose官方推荐的pre save中间件处理异步逻辑,配套修正bcrypt的调用方式:
- 简化虚拟属性定义,只做明文密码的临时缓存,不承载异步加密逻辑
- 修正密码加密方法的写法,使用bcrypt的Promise调用形式拿到真实加密hash
- 把加密操作移到pre save中间件中,利用中间件的异步执行能力完成加密存储
对应修正后的完整代码:
// 简化后的虚拟属性定义 UserSchema.virtual("password") .set(function (password) { // 仅临时存储明文密码到非持久化字段,不在这里做加密 this._password = password; }) .get(function () { return this._password; }); // 修正后的密码加密方法 UserSchema.methods = { returnSecurePassword: async function (password) { const saltRounds = 10; // 直接await bcrypt的Promise版本调用,不混用回调写法 return await bcrypt.hash(password, saltRounds); }, }; // 用pre save中间件承载异步加密逻辑 UserSchema.pre("save", async function (next) { // 仅当存在临时明文密码、且密码字段被修改时执行加密 if (!this._password || !this.isModified("password")) return next(); // 等待加密完成后存入实际持久化字段 this.encry_password = await this.returnSecurePassword(this._password); // 清空临时明文缓存,避免数据泄露 this._password = undefined; next(); });
注意事项
- 不要尝试通过hack手段让虚拟setter支持异步,Mongoose整个虚拟属性的处理链路是同步设计的,强行适配异步会在文档实例化、字段校验环节出现不可预期的问题。
- 使用pre save中间件的优势是,不管是调用
create、save还是其他走完整文档保存流程的方法,加密逻辑都会自动执行,不需要在每个业务接口里手动调用加密方法。
内容的提问来源于stack exchange,提问作者Bhumit 070
相关产品推荐
相关产品推荐

