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

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)来完成异步哈希,自定义类型仅负责同步验证和比较逻辑:

  1. 调整自定义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);
    };
};
  1. 在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 11:13:15