工厂函数的复杂类型返回 Typescript 中函数的包装器(泛型的泛型)

问题描述 投票:0回答:1

我想创建一个工厂函数,它接受 onOk 和 onErr 处理程序,并返回可能抛出异常的函数包装器。

const mayThrow = (fail: boolean) => {
   if (fail) {
      throw new Error('failed')
   }
   return 'ok'
}

让我们定义处理程序及其类型

type Ok<T> = { ok: true, value: T }
type Err<E> = { ok: false, error: E }
type Result<T, E> = Ok<T> | Err<E>
const onOk = <T>(value: T): Ok<T> => ({ ok: true, value })
const onErr = <E>(error: E): Err<E> => ({ ok: false, error })

我有这个工厂函数,我几乎可以正确输入。

function createSafeFactory<
   U extends (...args: any[]) => any,
   F extends (...args: any[]) => any
>(onOk: U, onErr: F) {
   return <A extends any[], T, E>(
      fn: (...args: A) => T,
      errorFn?: (error: unknown) => E
   ) => {
       return (...args: A): ReturnType<U> | ReturnType<F> => {
           try {
               const result = fn(...args)
               return onOk(result)
           } catch (e) {
               const error = errorFn ? errorFn(e) : (e as E)
               return onErr(error)
           }
       }
   }
}

const safe = createSafeFactory(onOk, onErr)
// const safe: <A extends any[], T, E>(fn: (...args: A) => T, errorFn?: ((error: unknown) => E) | undefined) => (...args: A) => Ok<unknown> | Err<unknown>

const safeFn = safe(mayThrow)
// const safeFn: (fail: boolean) => Ok<unknown> | Err<unknown>

const res = safeFn(true)
// const res: Ok<unknown> | Err<unknown>

if (res.ok) {
   const data = res.value
   // const data: unknown
}

数据未知而不是

string

我想要的是输入的结果

Result<string, never>
Ok<string> | Err<never>

问题就在这里:

ReturnType<U> | ReturnType<F>
。我想要
U
F
given 的返回类型,
U
F
是相对于
T
E
键入的。

我怎样才能实现类似

ReturnType<U<T>> | ReturnType<F<E>>

的目标
typescript
1个回答
0
投票

您无法执行任意高阶通用函数类型操作。一般来说,TypeScript 必须放弃并删除泛型类型参数,或者在操作它们之前用它们的约束实例化它们。因此,作用于类型 ReturnType<>

<T>(value: T) => Ok<T>
 将产生 
Ok<unknown>
,因为函数内部的泛型类型参数
T
最终会使用其隐式
unknown
约束进行实例化。 TypeScript 的类型系统对此的表达能力根本不够。为了允许这样的事情,您可能需要更高级的类型,如 microsoft/TypeScript#1213 中所述,或如 microsoft/TypeScript#50481 中请求的实例化类型。

value级别有一些对操作通用函数类型的支持。也就是说,有时您可以编写对泛型函数进行操作并生成不同泛型函数的泛型函数,但您需要实际的 functions 来执行此操作,而不仅仅是它们的类型。 此支持是在 microsoft/TypeScript#30215 中实现的,并且它所支持的内容非常有限。特别是:

当函数调用中的参数表达式属于泛型函数类型时,该函数类型的类型参数将传播到调用的结果类型,如果:

  • 被调用的函数是一个泛型函数,它返回具有单个调用签名的函数类型,
  • 单个调用签名本身并不引入类型参数,并且
  • 在函数调用参数的从左到右处理中,没有对参数表达式的上下文类型中引用的任何类型参数进行推断。

一般来说,您不能用

O extends (arg: any) => any
E extends (arg: any) => any
等函数类型来编写内容,而是需要根据参数和返回类型来编写内容,如
OA
OR
EA
 ER
。 不幸的是,这还不足以让您的代码开始工作:

function createSafeFactory<OA, OR, EA, ER>(
    onOk: (arg: OA) => OR,
    onErr: (arg: EA) => ER
) {
    return <A extends any[]>(
        fn: (...args: A) => OA,
        errorFn?: (error: unknown) => EA
    ) => {
        return (...args: A): OR | ER => {
            try {
                const result = fn(...args)
                return onOk(result)
            } catch (e) {
                const error = errorFn ? errorFn(e) : e as EA //🙃
                return onErr(error)
            }
        }
    }
}

const safe = createSafeFactory(onOk, onErr)
/* const safe: <A extends any[]>(
     fn: (...args: A) => unknown, 
     errorFn?: ((error: unknown) => unknown) | undefined
) => (...args: any[]) => Ok<unknown> | Err<unknown> */

(旁白:我不知道为什么您认为可以假设捕获的错误是在没有

onErr
参数的情况下所期望的错误。我认为您会在那里看到一些运行时错误,但这是题外话。)
哎呀,

errorFn

未能在

safe
onOk
类型中通用。为什么?哦,因为
onErr
返回一个函数,其调用签名引入了类型参数
createSafeFactory
。 如果我们将返回的函数设置为非泛型,那么您可以非常接近您正在寻找的内容:
A

这里 
function createSafeFactory<OA, OR, EA, ER>( onOk: (arg: OA) => OR, onErr: (arg: EA) => ER ) { return ( fn: (...args: any[]) => OA, errorFn?: (error: unknown) => EA ) => { return (...args: any[]): OR | ER => { try { const result = fn(...args) return onOk(result) } catch (e) { const error = errorFn ? errorFn(e) : e as EA //🙃 return onErr(error) } } } } const safe = createSafeFactory(onOk, onErr) /* const safe: <T, E>( fn: (...args: any[]) => T, errorFn?: ((error: unknown) => E) | undefined ) => (...args: any[]) => Ok<T> | Err<E> */

现在在

safe
T
中是通用的,从
E
fn
参数传播的泛型类型参数。返回类型为 `(...args: any[]) => Ok |犯错。这足以让您的示例发挥作用:
errorFn

但是

由于const safeFn = safe(mayThrow) const res = safeFn(true) // const res: Ok<string> | Err<unknown> if (res.ok) { const data = res.value // const data: string } 的返回类型忘记了

safe
的类型,你也可以做坏事,比如
args

我知道编写与类型类似的代码的唯一方法是 uncurry 
safeFn("oopsie", 123); // no error, oops

:

createSafeFactory

现在只有一个函数,
function createSafeFn<OA, OR, EA, ER, A extends any[]>( onOk: (arg: OA) => OR, onErr: (arg: EA) => ER, fn: (...args: A) => OA, errorFn?: (error: unknown) => EA ) { return (...args: A): OR | ER => { try { const result = fn(...args) return onOk(result) } catch (e) { const error = errorFn ? errorFn(e) : (e as EA) //🙃 be true? return onErr(error) } } }

的返回类型不会引入新的类型参数(

(...args: A): OR | ER
现在来自
A
)。 我们现在必须用更多参数来调用它,但我们终于得到了你想要的:
createSafeFn

const safeFn = createSafeFn(onOk, onErr, mayThrow) // const safeFn: <E>(fail: boolean) => Err<E> | Ok<string>

的错误调用是错误的:

safeFn

好的通话会产生你想看到的一切。

safeFn("oopsie", 123); // error, yay

因此,这已经是在没有语言新功能的情况下在 TypeScript 中所能达到的最接近的结果了。您可以编写作用于其他函数的函数,在一些相当严格的注意事项下,生成新的通用函数。您无法将其抽象到类型级别(您会发现 TypeScript 甚至不允许您将正在执行的操作编写为类型。它需要范围没有意义的类型参数,或者更高种类的泛型类型)。它很脆弱,并且处于 TypeScript 能力的边缘。

Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.