2

Prior to TypeScript 3.0, inferring types for N-arity functions involved creating an arbitrary number of function overloads for known generic arguments, then writing the function implementation using something like any typed rest parameters. TypeScript 3.0 changed this, so that a generic type can extend any[], which acts as a generic tuple.

This change makes sense and works fine for me with generic parameters themselves, but I can't get the types to flow through to return types as I could prior to 3.0.

Here's an example:

const takeToPromise = <T>(obs$: Observable<T>): Promise<T> => 
    obs$.pipe(take(1)).toPromise();

export function resolveObservables(): Promise<any[]>;
export function resolveObservables<T1>(o1: Observable<T1>): Promise<[T1]>;
export function resolveObservables<T1, T2>(o1: Observable<T1>, o2: Observable<T2>): Promise<[T1, T2]>;
export function resolveObservables<T1, T2, T3>(o1: Observable<T1>, o2: Observable<T2>, o3: Observable<T3>): Promise<[T1, T2, T3]>;
// Etc on the overloads
export function resolveObservables(...obs: (Observable<any>)[]): Promise<any[]> {
   return Promise.all(obs.map(takeToPromise));
}

Let's say I have only 3 overloads (up to T3). If I use resolveObservables by spreading an array into the function call, in TS 3.0, I will get an error:

Expected 0-3 arguments, but got 0 or more.

I can use this just fine with a fixed arity call, but as soon as I try to spread, I get errors. Any attempts to make this work have failed in some way or another. For example:

export function resolveObservables<T, U extends Observable<T>[]>(...obs: U): Promise<T[]>;

Will fail to capture any types (types are {}).

Is there some way to modify these types so I can get types to flow through without losing the ability to use spread arguments? In cases where I am using spread, I don't really care about the types, and if I did, I assume I could just use a tuple type. But based on what I've tried so far, I can either use spread but lose types, or have types but can't use spread.

What am I missing here?

1 Answer 1

2

We can get this to mostly work on 3.0, but for full tuple mapping support we will need to wait for 3.1.

Firstly you don't need the extra T parameter, just use a constraint of U extends Observable<any>[] This will infer the correct tuple type for U

To extract the result tuple type we can use a mapped and a conditional type to extract the result type from the observable. This is the part that almost works in 3.0. In 3.0 the mapped type will mangle the array type so that only indexing operations will still be OK on the resulting type, all methods will be of type never. 3.1 will make mapped types on tuples and arrays work more as expected (read PR for details) .

export function resolveObservables
    <U extends Observable<any>[]>(...obs: U): Promise<ExtractObservableTypes<U>> {
    return null as any;
}


type ExtractObservableTypes<T> = {
    [P in keyof T] : T[P] extends Observable<infer U> ? U : never
}
let o!: Observable<number>
let o2!: Observable<string>


resolveObservables(o, o2).then(r => {
    r[0].toExponential // r[0] is number
    r[1].substring // r[1] is string
})

Playground link

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for posting an answer that allows a signature that works. Just to clarify, just as you did here, when using ExtractObservableTypes<U> I have to cast to any (even using Promise.all). Is this because we don't have proper mapped tuples in 3.0? Basically why is ExtractObservableTypes<U> not assignable to Promise<any[]> according to TS 3? Thanks again.
I also found that in practice this doesn't allow destructuring. E.g. const r = await resolveObservables(foo$, bar$); const foo = r[0]; const bar = r[1]; works. But const [foo, bar] = await resolveObservables(foo$, bar$); Does not work b/c it's not an array type.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.