Issue
Given
type Loadable<T> = () => T
type LoadableCombinerResult<T> = { result: T }
I would like define types for a function with input like this:
- variable number of 
Loadable<ResponseDataType>inputs with differentResponseDataTypefor each input, combinertaking data results of above loadables.
The function would take care of handling error states and loading progress of loadables, plus few other things. The function would call combiner only if all loadables are successfully loaded.
This is possible to implement in untyped JavaScript. However, I fail to properly type it in TypeScript.
Non-variadic typing would look like this:
function useLoadableCombiner2<TResult, T1, T2>(
  combiner: (data1: T1, data2: T2) => TResult,
  loadable1: Loadable<T1>,
  loadable2: Loadable<T2>
): LoadableCombinerResult<TResult> { ... }
function useLoadableCombiner3<TResult, T1, T2, T3>(
  combiner: (data1: T1, data2: T2, data3: T3) => TResult,
  loadable1: Loadable<T1>,
  loadable2: Loadable<T2>,
  loadable3: Loadable<T3>
): LoadableCombinerResult<TResult> { ... }
function useLoadableCombiner4<TResult, T1, T2, T3, T4>(
  combiner: (data1: T1, data2: T2, data3: T3, data4: T4) => TResult,
  loadable1: Loadable<T1>,
  loadable2: Loadable<T2>,
  loadable3: Loadable<T3>,
  loadable4: Loadable<T4>
): LoadableCombinerResult<TResult> { ... }
function useLoadableCombinerN<...>(...): LoadableCombinerResult<TResult> { ... }
Is it possible to type this in TypeScript as one function in one declaration?
It could be an array/typed-tuple instead of variable number of arguments.
The goal is to put in variable number of loadables in, and then being able to call typed combiner with all the data, after a successful load of everything.
Solution
You can use a generic tuple type T to represent the rest parameter list to combiner, and then map over that tuple type to get the type of the loadable rest parameter:
declare function useLoadableCombiner<R, T extends any[]>(
  combiner: (...data: T) => R,
  ...loadable: { [I in keyof T]: Loadable<T[I]> }
): LoadableCombinerResult<R>;
Now when you call useLoadableCombiner, the type checker can infer T either from the parameter types of combiner or, if you don't annotate those, from the return types of each element of loadable:
const x = useLoadableCombiner(
  //  ^? const x: LoadableCombinerResult<boolean>
  (str, num, dat) => str.length + num > dat.getTime(),
  () => "a", () => 1, () => new Date()
)
Here T is inferred from loadable is of type [Loadable<string>, Loadable<number>, Loadable<Date>], and then combiner is checked (you can see how even though we don't annotate str, num, and dat, the type checker knows their types from loadable) and returns a boolean, and thus x is of type LoadableCombinerResult<boolean>.
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.