我想创建一个工厂函数,它接受 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 必须放弃并删除泛型类型参数,或者在操作它们之前用它们的约束实例化它们。因此,作用于类型 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