我编写了一个简单的管道函数,它接受异步函数,或者只接受传递而不执行的值。
我真的尝试过使用泛型来定义它,但没有让它恢复到使用unknown
来代替。
export const pipe = (...args: Array<unknown>): Promise<unknown> | unknown =>
args.reduce((prev, exec) => {
if (typeof exec !== 'function') {
return exec;
}
const getNextInPipe = async (): Promise<unknown> => {
return exec(await prev);
};
const value = getNextInPipe();
return value;
});
我试着这样写:
export const pipe = <T,>(...args: Array<unknown>): unknown =>
args.reduce((prev, exec) => {
if (typeof exec !== 'function') {
return exec;
}
const getNextInPipe = async (): Promise<T> => {
return exec(await prev);
};
const value = getNextInPipe();
return value;
});
但是我不知道如何替换另一个unknown
,是否可以做到?因为管道中每个函数的输出类型不依赖于输入类型。
我对仿制药还是新手,先谢了
1条答案
按热度按时间xn1cxnb41#
您的函数可能很简单(这是有争议的),但generic类型是什么都不是。你试图表示一个"链"的类型的任意长度。本质上你开始与类型
I
的初始值,然后可能是一个函数的类型像(input: Awaited<I>) => Promise<Awaited<TFirst>>
的一些输出类型TFirst
,然后可能是一个函数的类型像(input: Awaited<TFirst>) => Promise<Awaited<TSecond>>
,等等,等等,最后以(input: Awaited<TPenultimate>) => Promise<Awaited<TLast>>
类型的函数结束,然后pipe()
的输出是Promise<Awaited<TLast>>
类型的值,除非没有函数而只有输入I
,在这种情况下输出是I
。类型为
Awaited
的部分处理的是这样一个事实:如果你await
一个非承诺值,你会得到这个值,所以Awaited<string>
是string
,Awaited<Promise<string>>
是string
......你不能真正嵌套承诺,所以Awaited<Promise<Promise<string>>>
也是string
。因此,
pipe()
的一种方法如下所示:I
类型参数对应init
函数参数的类型,T
类型参数对应fns
rest参数中每个函数的输出类型的元组,所以如果有两个函数,第一个函数返回Promise<boolean>
,第二个函数返回string
,那么T
将是[Promise<boolean>, string]
。fns
参数的类型是复杂性所在的地方。对于fns
的numericlike索引N
处的元素(第一个为0
,第二个为1
),我们知道输出类型是T
的第N
个元素,或者是indexed access typeT[N]
,这很简单,但是输入类型来自T
的 * previous * 元素,或者是I
,我们首先用[I, ...T]
来表示它,它使用一个可变元组类型来表示将I
前置到T
。然后我们只需要其中的第N
个元素。概念上,这是索引访问[I, ...T][N]
。但是编译器不是'我不够聪明,无法意识到T
元组类型的每个数字索引N
也将是[I, ...T]
元组类型的索引,所以我需要使用Idx
帮助器类型来说服编译器执行该索引。至于输出类型,我们需要分解
T
来找到它的最后一个元素R
(使用条件类型推理),如果它存在,那么我们返回类型为Promise<Awaited<R>>
的值,如果不存在,那是因为T
为空,所以我们只返回I
。哇。
好的,让我们来测试一下。首先是支持的用途:
z
和x
是预期类型的承诺,而y
只是一个数字值。这些都是因为违反了函数的约束而失败的。它至少需要一个参数,并且只有第一个参数可以是非函数。每个函数都需要接受前一个函数等待的响应(或者初始值),如果不接受,就会得到一个错误。
所以这是我能做的最好的工作了。它并不完美;我确信如果你仔细看的话你会发现一些边缘情况,最明显的一个就是如果你不注解回调参数,那么推理可能会失败,比如
pipe(10, x => x.toFixed(), y => y.toFixed())
应该产生一个错误,但是没有,因为编译器无法推断x
应该是一个number
,它退回到any
。之后所有的输入和输出都是any
,如果你想让它被捕获,你需要写pipe(10, (x: number)=>x.toFixed(), (y: number)=>y.toFixed())
,可能有一些调整可以改进这个,但是我不打算在这里花更多的时间去寻找它们。主要的一点是,你可以表示这种东西,但它并不简单。
Playground代码链接