Mongoose自定义Password类型cast方法返回Promise<pending>的TypeScript错误
问题分析与解决方案
核心问题
Mongoose的SchemaType.cast方法要求同步执行,其返回类型定义为具体值(如string)而非Promise<string>。你用async/await修饰cast函数后,它会返回一个Promise,这既不符合TypeScript类型约束,也导致数据库中存入了未解析的Promise { <pending> }。
解决方案
方案1:使用bcrypt同步哈希(最直接)
直接替换异步的bcrypt.hash为同步的bcrypt.hashSync,让cast方法返回字符串类型的哈希值,完全符合Mongoose的要求:
import bcrypt from "bcrypt"; import mongoose, { SchemaTypeOptions } from "mongoose"; const SALT_ROUNDS = 10; export default class Password extends mongoose.SchemaType { constructor(key: string, options: SchemaTypeOptions<string>) { super(key, options, "Password"); }; cast(value: string) { // 同步哈希密码,返回字符串 const hash = bcrypt.hashSync(value, SALT_ROUNDS); return hash; }; async compare(password: string, hash: string) { return bcrypt.compare(password, hash); }; };
方案2:用预存钩子处理异步哈希(更符合Mongoose最佳实践)
如果坚持使用异步哈希逻辑,不要在cast方法中处理,而是改用Mongoose的预存中间件(pre-save hook)来完成异步哈希,自定义类型仅负责同步验证和比较逻辑:
- 调整自定义Password类型:
import bcrypt from "bcrypt"; import mongoose, { SchemaTypeOptions } from "mongoose"; export default class Password extends mongoose.SchemaType { constructor(key: string, options: SchemaTypeOptions<string>) { super(key, options, "Password"); }; cast(value: string) { // 这里做同步验证,比如检查密码长度 if (value.length < 8) { throw new Error('密码长度至少为8位'); } return value; // 返回明文,后续由钩子处理哈希 }; async compare(password: string, hash: string) { return bcrypt.compare(password, hash); }; };
- 在Schema中添加预存钩子:
const UserSchema = new mongoose.Schema({ username: String, password: Password }); // 新建或更新文档时自动哈希密码 UserSchema.pre('save', async function(next) { // 仅当密码字段被修改时才重新哈希 if (!this.isModified('password')) return next(); this.password = await bcrypt.hash(this.password, 10); next(); }); // 处理findOneAndUpdate的情况(pre-save不触发此操作) UserSchema.pre('findOneAndUpdate', async function(next) { const update = this.getUpdate() as { password?: string }; if (update.password) { update.password = await bcrypt.hash(update.password, 10); } next(); });
关键说明
Mongoose的cast方法设计初衷是做同步的类型转换与基础验证,比如把输入转换成符合要求的类型格式,异步操作会打乱Mongoose的内部数据处理流程,导致未解析的Promise被存入数据库。异步逻辑应该交给Mongoose的中间件(如pre-save)来处理,这是官方推荐的异步数据处理方式。
内容的提问来源于stack exchange,提问作者Sikandar Moyal
相关产品推荐
相关产品推荐

