I have a function called pipeline() It's currently typed as so
function pipeline(firstFn: Function, ...fns: Function[]) {
let previousFn = firstFn()
for (const func of fns) {
previousFn = func(previousFn)
}
return previousFn
}
If I use it to pipeline some functions
const add = (x: number) => (y: number) => x+y
const result = pipeline(
() => 4,
add(2),
add(4)
)
// 10
However, with this solution there is not type-checking that the argument types of the functions match the return types of the previous functions, and the return type is simple any.
I realize this can't be completely typed (eg, Promise.all([]) has a max of 10 promises it can deal with before it gives up) but I'd like to have type-checking on invocations of at least a few arguments.
In the case where the return type of each function in the pipeline is the same, this can be achieved with generics fairly easily:
function pipeline<T>(firstFn: () => T, ...fns: ((arg: T) => T)[]): T {
let previousFn = firstFn()
for (const func of fns) {
previousFn = func(previousFn)
}
return previousFn
}
The first thing we need to understand is how does Promise.all to it?
From the typescript source lib.es2015.promise.d.ts:
all<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>, T10 | PromiseLike<T10>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>;
all<T1, T2, T3, T4, T5, T6, T7, T8, T9>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>;
all<T1, T2, T3, T4, T5, T6, T7, T8>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8]>;
...
all<T>(values: (T | PromiseLike<T>)[]): Promise<T[]>;
They've written an overload for the function for every number of elements starting with 10 and going all the way down to the generic catch-all.
This is why Promise.all strict typing on the returned array bails out at more than 10 differently typed elements.
If you want to replicate this behaviour, you'll need to go to the same effort. Here's what this would look like for up to three parameters:
interface PipelineInterface {
<T0, T1, T2>(a0: () => T0, a1: (a: T0) => T1, a2: (a: T1) => T2): T2;
<T0, T1>(a0: () => T0, a1: (a: T0) => T1): T1;
<T>(firstFn: () => T, ...fns: ((arg: T) => T)[]): T;
}
const pipeline: PipelineInterface = function (firstFn, ...fns) {
let previousFn = firstFn()
for (const func of fns) {
previousFn = func(previousFn)
}
return previousFn
}
const result = pipeline(
() => 4,
(a) => a + 'Hello'
)
// result has type string
const result2 = pipeline(
() => 4,
(a: string) => a + 'Hello'
)
// Argument of type '(a: string) => string' is not assignable to parameter of type '(a: number) => string'.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With