保留输入/输出类型的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,没有三个类型,每个类型都有自己的泛型吗?a
NexterChain
是一个构建器,它生成一个
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]>;