保留输入/输出类型的TypeScript中的函数链工厂?
考虑一下这个金字塔代码:保留输入/输出类型的TypeScript中的函数链工厂?,typescript,functional-programming,yield,Typescript,Functional Programming,Yield,考虑一下这个金字塔代码: /** *@T,U *@param{T}数据 *@param{(数据:T)=>Promise}fn */ 函数makeNexter(数据,fn){ 返回{ 数据, 下一步:async()=>fn(数据), }; } 返回makeNexter({},异步(数据)=>{ 返回makeNexter({…data,a:3},async(data)=>{ 返回makeNexter({…data,b:'hi'},async(data)=>{ }); }); }); 是否有一种方
/**
*@T,U
*@param{T}数据
*@param{(数据:T)=>Promise}fn
*/
函数makeNexter(数据,fn){
返回{
数据,
下一步:async()=>fn(数据),
};
}
返回makeNexter({},异步(数据)=>{
返回makeNexter({…data,a:3},async(data)=>{
返回makeNexter({…data,b:'hi'},async(data)=>{
});
});
});
是否有一种方法可以生成一个函数,该函数在数组中接受无限多个函数,并将每个函数的结果按顺序应用于下一个函数,同时保留类型信息,以便内部函数上的每个数据参数都具有正确的推断类型
基本上,我试图使代码扁平化,而不是金字塔形,同时在每个参数中保留类型信息
从另一个角度来看,我试图创建一种生成器函数,其生成的类型不需要在每一步断言/过滤(在函数生成器通常返回的类型联合之外),而是在每一步都明确知道,因为它们总是线性传递的
换句话说,是否可以使用正确的类型信息在TypeScript中创建此函数
/**@type{???}*/
返回makeNexters({}[
异步(数据)=>{
返回{…数据,a:3};
},
异步(数据)=>{
返回{…数据,b:'hi'};
},
异步(数据)=>{
//此处的数据应具有以下类型:
// {
//b:弦;
//a:数字;
// }
},
]);
另请参见TypeScript问题中的我的功能请求:我怀疑您是否能够想出一个解决方案,在调用函数时,编译器可以按照您想要的方式推断类型,而无需任何额外工作 TypeScript有一个设计限制,在中突出显示,当这些推断需要相互依赖时,编译器无法同时推断回调参数的泛型类型参数和上下文类型。当你打电话时:
return makeNexters({}, [
async (data) => { return { ...data, a: 3 }; },
async (data) => { return { ...data, b: 'hi' }; },
async (data) => {},
])
您可能要求编译器使用{}
来推断某些泛型类型参数(或其中的一部分),该参数将用于推断第一个数据
回调参数的类型,然后该参数将用于推断某些泛型类型参数(或其中的一部分),然后,它将用于推断第二个数据回调参数的类型,等等。但是编译器只执行有限数量的类型推断阶段,并且在推断出其中的前一个或两个之后会放弃
从概念上讲,我将makeNexters()
的类型表示为:
type Idx<T, K> = K extends keyof T ? T[K] : never
declare function makeNexters<T, R extends readonly any[]>(
init: T, next: readonly [...{ [K in keyof R]: (data: Idx<[T, ...R], K>) => Promise<R[K]> }]
): void;
const p = makeNexterChain({})
.and(async data => ({ ...data, a: 3 }))
.and(async data => ({ ...data, b: "hi" }))
.done
或者,您可以放弃泛型类型推断,为回调参数获得相当好的上下文类型推断:
makeNexters<{}, [{ a: number }, { b: string, a: number }, void]>({}, [
async (data) => { return { ...data, a: 3 }; },
async (data) => { return { ...data, b: 'hi' }; },
async (data) => { }
]);
假设任何需要写出类型{b:string,a:number}
的代码都会破坏这个链接函数的功能
在完全放弃之前,我建议你考虑改变你的方法,使之通过返回另一个函数的函数来工作。从本质上讲,这是一种形式的,其中不是一次创建“nexter”,而是通过单个数组表示链,而是分阶段执行,其中链中的每个链接都由函数调用伪造。这些调用不是嵌套的,所以它不再是“金字塔”。您可以这样使用它:
type Idx<T, K> = K extends keyof T ? T[K] : never
declare function makeNexters<T, R extends readonly any[]>(
init: T, next: readonly [...{ [K in keyof R]: (data: Idx<[T, ...R], K>) => Promise<R[K]> }]
): void;
const p = makeNexterChain({})
.and(async data => ({ ...data, a: 3 }))
.and(async data => ({ ...data, b: "hi" }))
.done
产生的p
将是
/*const p: {
data: {};
next: () => Promise<{
data: {
a: number;
};
next: () => Promise<{
data: {
b: string;
a: number;
};
next: () => Promise<void>;
}>;
}>;
} */
实际上,您希望makeNexterChain()
获取类型为T
的初始元素,并返回NexterChain
。每个NexterChain
(其中R
是元组类型)都有一个和()。Nexter
具有一个data
属性,其类型是R
的第一个元素,以及一个无参数的next()
方法,该方法生成一个新Nexter
的承诺,其中T
与R
相同,且第一个元素已删除。哦,当它结束时,你会得到void
您可以看到,在这里,类型推断确实非常有效。在每个步骤中,编译器都知道数据
回调参数是什么,您不需要手动指定任何泛型参数或手动注释任何回调参数。希望您可以使用这样的解决方案,它可以与TypeScript的类型推断功能协同工作,而不是对抗TypeScript的类型推断功能
我一直在完全关注您,直到makeNexterChain
的类型定义,在这一点上,它似乎过于复杂了。函数的类型不能只需要一个类型,有两个类型,t代表输入,U代表输出,但返回一个递归类型,其输出U变成输入t,没有三个类型,每个类型都有自己的泛型吗?aNexterChain
是一个构建器,它生成一个Nexter
,每一个都需要跟踪组成完整链的类型R
的元组。每次调用和()。我不知道如何将元组链消除到单个输入输出关系中。您可以消除Last
,它只是一种类似的实用程序类型,但我没有看到比这更简单的东西。这可能没关系,但我只是想确保上面的makeNexterChain()
确实是可实现的。我的实现看起来像,而且似乎按照预期工作。
type Nexter<R extends any[]> = R extends [infer H, ...infer T] ? {
data: H
next: () => Promise<Nexter<T>>
} : void
type Last<T> = T extends [...infer F, infer L] ? L : never
interface NexterChain<R extends any[]> {
and<U>(cb: (data: Last<R>) => Promise<U>): NexterChain<[...R, U]>
done: Nexter<R>
}
declare function makeNexterChain<T>(init: T): NexterChain<[T]>;