异常的联合类型

Union Type for Exceptions

我正在寻找可能会滥用 TypeScript 类型系统的解决方案。

我有一个为存储库提供端口的服务(存储库必须实现的接口),因为该服务不能知道存储库的具体实现。由于这个事实,接口还必须提供服务可以处理的错误的定义。

为此我使用 ts-results.

我可以轻松地将错误定义为字符串,但我想在出现错误时从存储库向服务提供一些进一步的信息。 所以我尝试将错误定义为各种错误的联合类型 类。问题是每个默认错误都与更专业的错误的签名相匹配。

因此,在存储库中,无法阻止将任何其他错误传递给服务(端口)。

// The Port Definition
export abstract class FriendshipRepositoryPort implements IFriendshipRepository {
    abstract load(
        userIdA: UserId, userIdB: UserId
    ): Promise<Result<IFriendshipAggregate, FriendshipRepositoryErrors>>;

    abstract persist(
        friendship: IFriendshipAggregate
    ): Promise<Result<void, FriendshipRepositoryErrors>>;
}
// repo implementation
async persist(friendship: IFriendshipAggregate): Promise<Result<void, FriendshipRepositoryErrors>> {
        // ... preparing persisting the entities
        try {
            return new Ok(await this._persistenceManager.execute(querySpec));
        } catch (e) {
            console.error(e);
            // FIXME: This should not be possible!
            return new Err(new RuntimeError());
        }
    }
// error definition
export type FriendshipRepositoryErrors = UserNotFoundError
    | DomainRuleViolation
    | DatabaseWriteError
    | DatabaseReadError;

是否有任何方法可以将结果定义为仅接受给定 类(或继承人)的实例作为错误类型?

我还创建了一个 playground 来在一个非常小的例子中演示这个问题。

我认为这里最好的解决方案是使用discriminated union types。 虽然这看起来很麻烦,但它可能是最安全和明确的方式来告诉可能会发生什么错误,更具体地说,哪些错误可以作为错误注入到您的结果中。

查看this playglound编译器的行为方式。

class ErrorA extends Error{
    #type = ErrorA.name;
}
class ErrorB extends Error {
    #type = ErrorB.name;
}

class NotAllowedError extends Error {
}

class ErrorC extends ErrorB {
    #type = ErrorC.name;
}

type AllowedErrorTypes =  ErrorA | ErrorB;

function handleError(error: AllowedErrorTypes): void {
    if(error instanceof ErrorA) {
        throw error;
    }
    if(error instanceof ErrorB) {
        throw error;
    }
    // no other options left.
    // If I add another allowedError the compiler is going to complain
    const exhausted: never = error;
}

handleError(new ErrorA());
handleError(new ErrorB());
handleError(new NotAllowedError());
handleError(new ErrorC());