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

如何基于异步函数设置类属性?TypeScript类型推断疑问

解决方案

1. 静态异步工厂方法(推荐)

直接用静态异步方法创建并初始化实例,外部无需手动调用init,拿到的实例天然保证itemCategories已经赋值,TypeScript能直接推断出属性非空。

修改后的代码:

import type { Application } from '../application';

export class ShopManager {
    private readonly _application: Application;
    private _itemCategories: string[]; // 无需再声明|null

    public get itemCategories() {
        return this._itemCategories;
    }
    
    // 构造函数改为私有,强制通过工厂方法创建实例
    private constructor(application: Application) {
        this._application = application;
    }

    // 静态异步工厂方法,负责初始化并返回实例
    public static async create(application: Application): Promise<ShopManager> {
        const manager = new ShopManager(application);
        manager._itemCategories = await manager._getItemCategories();
        return manager;
    }

    private async _getItemCategories(): Promise<string[]> {
        const categories = await this._application.storage.itemCategory.findMany();
        return categories.map((category) => category.name);
    }
}

使用方式:

// 直接获取已初始化的实例
const shopManager = await ShopManager.create(application);
// 此时itemCategories是确定的string[],无需空检查
console.log(shopManager.itemCategories.length);

这个方案从根源上避免了未初始化状态,外部调用更简洁,类型也更安全。

2. 类型守卫/断言函数(兼容现有结构)

如果不想改动现有类的构造方式,可以添加类型守卫或断言函数,让TypeScript识别实例是否已初始化,从而跳过空检查。

修改后的代码:

import type { Application } from '../application';

export class ShopManager {
    private readonly _application: Application;
    private _itemCategories: string[] | null = null;

    public get itemCategories() {
        return this._itemCategories;
    }
    
    public constructor(application: Application) {
        this._application = application;
    }

    public async init() {
        this._itemCategories = await this._getItemCategories();
    }

    // 类型守卫函数,判断实例是否已初始化
    public isInitialized(): this is ShopManager & { itemCategories: string[] } {
        return this._itemCategories !== null;
    }

    // 断言函数,直接告知TypeScript实例已初始化(适合确定已调用init的场景)
    public assertInitialized(): asserts this is ShopManager & { itemCategories: string[] } {
        if (this._itemCategories === null) {
            throw new Error('ShopManager has not been initialized. Call init() first.');
        }
    }

    private async _getItemCategories(): Promise<string[]> {
        const categories = await this._application.storage.itemCategory.findMany();
        return categories.map((category) => category.name);
    }
}

使用方式:

const shopManager = new ShopManager(application);
await shopManager.init();

// 用类型守卫缩小类型
if (shopManager.isInitialized()) {
    console.log(shopManager.itemCategories.length); // 无需空检查
}

// 或者用断言函数
shopManager.assertInitialized();
console.log(shopManager.itemCategories.length); // 无需空检查

这个方案适合需要保留现有类结构的场景,通过类型工具让TypeScript识别初始化状态,减少冗余的空检查。

3. 可选:使用Promise作为属性类型

如果业务允许延迟获取属性值,可以把_itemCategories设为Promise<string[]>,省去单独的init方法,属性本身就是异步的:

import type { Application } from '../application';

export class ShopManager {
    private readonly _application: Application;
    private readonly _itemCategories: Promise<string[]>;

    public get itemCategories() {
        return this._itemCategories;
    }
    
    public constructor(application: Application) {
        this._application = application;
        this._itemCategories = this._getItemCategories();
    }

    private async _getItemCategories(): Promise<string[]> {
        const categories = await this._application.storage.itemCategory.findMany();
        return categories.map((category) => category.name);
    }
}

使用方式:

const shopManager = new ShopManager(application);
// 使用时需要await
const categories = await shopManager.itemCategories;
console.log(categories.length);

这个方案适合不需要立即同步获取属性的场景,省去了init调用,但每次使用属性都需要await。

内容的提问来源于stack exchange,提问作者Viriato

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 00:55:04