我想创建一个函数,它可以获得一个可选的errorCallback参数.如果该函数的使用者没有提供回调,我想使用缺省回调.此函数需要强类型,并使用消费者自定义的errorCallback‘S返回类型的返回类型,否则使用我的自定义错误处理程序的返回类型.

以下是代码:

export enum ErrorType {
    VALIDATION_ERROR,
    API_ERROR,
    UNKNOWN_ERROR,
    UNAUTHORIZED
}

type MockZodType ={}

type MockResponseError = {}

export type ServiceError<TSchema extends MockZodType> =
    | {
            type: ErrorType.VALIDATION_ERROR;
            message: string;
            status: number;
            data: TSchema;
            originalError: MockResponseError;
      }
    | {
            type: ErrorType.API_ERROR;
            message: string;
            status: number;
            data: unknown;
            originalError: MockResponseError;
      }
    | {
            type: ErrorType.UNKNOWN_ERROR;
            message: string;
            status: number;
            data: unknown;
            originalError: unknown;
      }
    | {
            type: ErrorType.UNAUTHORIZED;
            message: string;
            status: number;
            data: unknown;
            preventDefaultHandler: boolean;
            originalError: MockResponseError;
      };

export type ServiceCallOptions<
    TServiceCallReturn extends Promise<unknown>,
    TSchema extends MockZodType = never,
    TErrorCallbackReturn extends Promise<unknown> = Promise<ServiceError<TSchema>>
> = {
    serviceCall: () => TServiceCallReturn;
    errorSchema?: TSchema;
    errorCallback?: (e: ServiceError<TSchema>) => TErrorCallbackReturn;
};

export async function callService<
    TServiceCallReturn extends Promise<unknown>,
    TSchema extends MockZodType = never,
    TErrorCallbackReturn  extends Promise<unknown> = Promise<ServiceError<TSchema>>
>({
    serviceCall,
    errorSchema,
    errorCallback = async (e)=> {
        if (e.type === ErrorType.UNAUTHORIZED && !e.preventDefaultHandler) {
            // some custom default handler;
        }
        return e; 
    }
}: ServiceCallOptions<TServiceCallReturn, TSchema, TErrorCallbackReturn>) {
    // some logic
}

错误来自errorCallback的默认参数值,当它说:

Type '(e: ServiceError<TSchema>) => Promise<ServiceError<TSchema>>' is not assignable to type '(e: ServiceError<TSchema>) => TErrorCallbackReturn'.
  Type 'Promise<ServiceError<TSchema>>' is not assignable to type 'TErrorCallbackReturn'.
    'Promise<ServiceError<TSchema>>' is assignable to the constraint of type 'TErrorCallbackReturn', but 'TErrorCallbackReturn' could be instantiated with a different subtype of constraint 'Promise<unknown>'.

我不想使用超载.为什么这不管用呢?

这是ts playground的链接.

推荐答案

问题在于,从技术上讲,指定类型参数的是callsgeneric函数,而不是writes个函数.调用方可能永远不会有意这样做,因为编译器通常可以infer类型参数,但这种推断是代表调用方而不是实现者进行的.当无法推断类型参数时,实现者可以 Select default个类型参数,而调用者始终可以 Select 以符合其constraints的任何方式手动指定类型参数.

因此,即使您的意图是当调用方省略errorCallback属性时,编译器将使用Promise<ServiceError<TSchema>>作为TErrorCallbackReturn类型参数的类型实参,但您不能保证这一点.特别精神错乱的来电者可能会这样做:

const f = await callService<
    Promise<string>, {}, Promise<number>
>({ serviceCall: async () => "a" });

在这里,调用者声称丢失的errorCallback属性将返回number.但你的default argument-async (e) => {⋯}并非如此,它返回的是ServiceError<TSchema>.事实上,无论TErrorCallbackReturn类型参数是什么,它都会返回ServiceError<TSchema>,这就是编译器抱怨的原因.这就是为什么

'Promise<ServiceError<TSchema>>' is assignable to the constraint of 
type 'TErrorCallbackReturn', but 'TErrorCallbackReturn' could be 
instantiated with a different subtype of constraint 'Promise<unknown>'

意思是.


那么,你能做什么呢?Overloads是一种方法,这样你就可以显式地控制调用者看到的可用的调用签名.这将使调用方不可能传递第三个类型参数,除非它们还传递预期的errorCallback属性.

async function callService<
    TServiceCallReturn extends Promise<unknown>,
    TSchema extends MockZodType = never,
>(arg: {
    serviceCall: () => TServiceCallReturn, errorSchema?: TSchema,
    errorCallback?: never
}): Promise<void>;

async function callService<
    TServiceCallReturn extends Promise<unknown>,
    TSchema extends MockZodType = never,
    TErrorCallbackReturn extends Promise<unknown> = Promise<ServiceError<TSchema>>
>(arg: {
    serviceCall: () => TServiceCallReturn, errorSchema?: TSchema,
    errorCallback: (e: ServiceError<TSchema>) => TErrorCallbackReturn
}): Promise<void>;

但是,如果您不想进行太多重构,您可以直接说,您认为不需要担心糟糕的调用,并且您认为默认回调参数的类型是正确的.可能是这样的:

async function callService<
    TServiceCallReturn extends Promise<unknown>,
    TSchema extends MockZodType = never,
    TErrorCallbackReturn extends Promise<unknown> = Promise<ServiceError<TSchema>>
>({
    serviceCall,
    errorSchema,
    errorCallback = (async (e: ServiceError<TSchema>) => {
        if (e.type === ErrorType.UNAUTHORIZED && !e.preventDefaultHandler) {
            // some custom default handler;
        }
        return e;
    }) as (e: ServiceError<TSchema>) => TErrorCallbackReturn
}: ServiceCallOptions<TServiceCallReturn, TSchema, TErrorCallbackReturn>) { }

这修复了编译器错误,只要您的断言保持有效,一切都应该如您所预期的那样工作.

Playground link to code

Typescript相关问答推荐

类型是工作.但它失go 了正确变体的亮点..为什么?或者如何增强?

角形标题大小写所有单词除外

Webpack说你可能需要在Reactjs项目中增加一个加载器''

如何键入函数以只接受映射到其他一些特定类型的参数类型?

如何在TypeScrip中使用嵌套对象中的类型映射?

使用泛型keyof索引类型以在if-condition内类型推断

编剧错误:正在等待Expect(Locator).toBeVisible()

TypeScrip:来自先前参数的参数中的计算(computed)属性名称

在列表的指令中使用intersectionObservable隐藏按钮

在保留类型的同时进行深层键名转换

将超类型断言为类型脚本中的泛型参数

三重融合视角与正交阵

无法缩小深度嵌套对象props 的范围

类型脚本中函数重载中的类型推断问题

如何使对象同时具有隐式和显式类型

如何在Vuetify 3中导入TypeScript类型?

React-Router V6 中的递归好友路由

TS2532:对象可能未定义

redux-toolkit、createAsyncThunk 错误

Mongodb TypeError:类继承 events_1.EventEmitter 不是对象或 null