TypeScript 中动态导入类的 instanceof 检查失败的问题

Issue with instanceof Checks Failing for Dynamically Imported Classes in TypeScript

提问人:Pablo Fonseca 提问时间:11/17/2023 最后编辑:Pablo Fonseca 更新时间:11/19/2023 访问量:42

问:

我正在处理一个 TypeScript 项目,我在其中动态导入 Discord 机器人的类,但我遇到了一个问题,即 instanceof 检查在运行时失败,即使设置中一切看起来都是正确的。

环境:

Node.js 版本:20.9.0
TypeScript 版本:5.2.2
项目设置:使用 discord.js 的 Discord 机器人

问题:正在返回应属于特定类的实例的检查。尽管 TypeScript 编译未显示任何错误,但该问题发生在运行时环境中。instanceoffalse

代码概述:我有一个设置,其中 Discord 机器人的命令和事件是从文件中动态加载的。主要类结构如下:

ReadableCommandObject并且是基类。ReadableEventObject

特定命令,如 . 该方法动态导入这些命令类。Ping extends ReadableCommandObjectObjectFetcher.read

Ping 命令示例:

下面是一个命令类的示例:

import { type CommandInteraction, SlashCommandBuilder, type CacheType } from 'discord.js';
import ReadableCommandObject from '../../../../utilities/ObjectFetcher/ReadableCommandObject';


class Ping extends ReadableCommandObject {
    data: SlashCommandBuilder;
    execute: (interaction: CommandInteraction<CacheType>) => Promise<any>;

    constructor () {
        super();
        this.data = new SlashCommandBuilder()
        .setName('ping')
        .setDescription('Replies pong!');

        this.execute = async (interaction: CommandInteraction<CacheType>) => {
            return await interaction.reply('Pong!');
        };
    }

    getReadableData (): unknown {
        return {
            data: this.data,
            execute: this.execute
        };
    };
}

export default Ping;

对象获取方法:

/* eslint-disable @typescript-eslint/no-extraneous-class */
import type IReadableObject from './IReadableObject';

class ObjectFetcher {
    static async read (filePath: string): Promise<IReadableObject> {
        try {
            if (!filePath || filePath.length === 0) {
                throw new Error('The file path provided is empty');
            }

            const readableObjectClass = await require(filePath).default;
            const readableObject = new readableObjectClass();
            return readableObject;
        } catch (error) {
            console.log('There was an error while fetching the object: ', error);
            throw error;
        }
    }
}

/* eslint-enable @typescript-eslint/no-extraneous-class */

export default ObjectFetcher;

问题复制:

在运行时,当我尝试检查导入的对象是否是 or 的实例时,检查失败:ReadableCommandObjectReadableEventObject

// Asynchronously crawls specified directories for command and event JS files, dynamically loading and processing each found file.
const objectFetchResolver = async () => {
    const filePaths: string[] = ['core/models/commands', 'core/models/events'];
    for (const filePath of filePaths) {
        const crawler = new FileCrawler(path.join(__dirname, filePath), '.js', true, true);
        const objectFilePaths = await crawler.crawl();
        for (const objectFilePath of objectFilePaths) {
            const objectInFile = await ObjectFetcher.read(objectFilePath);
            if (objectInFile instanceof ReadableCommandObject) {
                console.log('Entered in ReadableCommandObject');
                commandObjectAdderModerator(objectInFile, objectFilePath);
            } else if (objectInFile instanceof ReadableEventObject) {
                console.log('Entered in ReadableEventObject');
                eventObjectAdderModerator(objectInFile, objectFilePath);
            } else {
                console.log('Nothing matched');
            }
        }
    };
};

输出

Nothing matched
Nothing matched
Nothing matched

其他上下文:

有趣的是,当我在调试器中运行相同的代码时(例如,使用 Visual Studio Code 的调试器或 Node.js 的调试器),检查按预期工作,并且我得到了正确的数据。但是,当正常运行应用程序(在调试器外部)时,检查将失败。此行为表明问题可能与环境或执行上下文有关。--inspectinstanceofinstanceof

调试器中的特定行为:

调试时,返回 .
在正常执行中,相同的检查返回 .
问:
是什么原因导致调试器和正常运行时之间的检查行为存在差异?Node.js 执行环境或 TypeScript 的编译过程是否存在特定方面在调试上下文中可能的行为不同?
objectInFile instanceof ReadableCommandObjecttruefalseinstanceof

  • 已确保文件路径和导出正确无误。
  • 检查并重新编译了 TypeScript 代码。
  • 已检查编译的JavaScript代码是否存在任何异常。
  • 已验证 Node.js 和 TypeScript 版本。

解决方案更新

在探索了各种潜在的解决方案并考虑了反馈和建议之后,我决定将我的方法从使用 instanceof 转变为利用 TypeScript 的类型保护和接口。此更改更符合 TypeScript 的设计模式,并解决了我面临的问题。

新方法:

TypeScript 类型保护:实现了自定义类型保护,以动态检查对象的类型。此方法提供了一种更可靠且面向 TypeScript 的方法来确保对象的类型正确。

接口和类型检查:使用接口来定义命令和事件对象的结构。这种方法不仅使代码更具可读性和可维护性,而且还利用了 TypeScript 静态类型检查的全部功能。

示例实现:

以下是我如何实现这一点的简短示例:

import { type SlashCommandBuilder, type CommandInteraction } from 'discord.js';

interface ICommand {
    type: 'command'
    data: SlashCommandBuilder
    execute: (interaction: CommandInteraction) => Promise<any>
}

export default ICommand;

import type IReadableObject from '../readable-object';
import type ICommand from '../commands/command';

function isCommand (obj: IReadableObject): obj is ICommand {
    return obj.type === 'command';
}

export default isCommand;

// Usage
const loadObjects = async () => {
    const filePaths = ['core/models/commands', 'core/models/events'];

    for (const file of filePaths) {
        const crawler = new FileCrawler(path.join(__dirname, file), '.js', true, true);
        const objectFilePaths = await crawler.crawl();
        for (const objectFilePath of objectFilePaths) {
            const object: ReadableObject = await ObjectFetcher.read(objectFilePath);
            readableObjectHandler(object);
        }
    }
};

// So here I just handle the type of each and accordingly assign the adder moderator
const readableObjectHandler = (readableObject: ReadableObject): void => {
    if (isCommand(readableObject)) {
        commandObjectAdderModerator(readableObject);
    } else if (isEvent(readableObject)) {
        eventObjectAdderModerator(readableObject);
    }
};

这种方法有效地解决了我在 instanceof 检查中遇到的运行时问题,并且它更自然地融入了 TypeScript 生态系统。

节点.js 字稿 不和谐.js

评论


答: 暂无答案