1

I am trying to create a function that takes an array of observables and return their values mapping them to an array of their values but I am having problems with the overloads.

I have defined 6 overloads, each overload takes one array item more, and as general a regular array with 'n' items.

But I get the problem:

No overload matches this call.
  The last overload gave the following error.
    Argument of type 'Observable<number>[]' is not assignable to parameter of type '[Observable<number>, Observable<number>, Observable<number>, Observable<number>, Observable<number>, Observable<...>]'.
      Target requires 6 element(s) but source may have fewer.

I have made a stackblizt where the error happens, and this is the code:

import { combineLatest, Observable, of, Subscription } from 'rxjs';

export type ValueOfObservable<T> = T extends Observable<infer U> ? U : never;

export function myFunction$<O1 extends Observable<unknown>>(
  sources: [O1]
): Observable<[ValueOfObservable<O1>]>;
export function myFunction$<
  O1 extends Observable<unknown>,
  O2 extends Observable<unknown>
>(
  sources: [O1, O2]
): Observable<[ValueOfObservable<O1>, ValueOfObservable<O2>]>;
export function myFunction$<
  O1 extends Observable<unknown>,
  O2 extends Observable<unknown>,
  O3 extends Observable<unknown>
>(
  sources: [O1, O2, O3]
): Observable<
  [ValueOfObservable<O1>, ValueOfObservable<O2>, ValueOfObservable<O3>]
>;
export function myFunction$<
  O1 extends Observable<unknown>,
  O2 extends Observable<unknown>,
  O3 extends Observable<unknown>,
  O4 extends Observable<unknown>
>(
  sources: [O1, O2, O3, O4]
): Observable<
  [
    ValueOfObservable<O1>,
    ValueOfObservable<O2>,
    ValueOfObservable<O3>,
    ValueOfObservable<O4>
  ]
>;
export function myFunction$<
  O1 extends Observable<unknown>,
  O2 extends Observable<unknown>,
  O3 extends Observable<unknown>,
  O4 extends Observable<unknown>,
  O5 extends Observable<unknown>
>(
  sources: [O1, O2, O3, O4, O5]
): Observable<
  [
    ValueOfObservable<O1>,
    ValueOfObservable<O2>,
    ValueOfObservable<O3>,
    ValueOfObservable<O4>,
    ValueOfObservable<O5>
  ]
>;
export function myFunction$<
  O1 extends Observable<unknown>,
  O2 extends Observable<unknown>,
  O3 extends Observable<unknown>,
  O4 extends Observable<unknown>,
  O5 extends Observable<unknown>,
  O6 extends Observable<unknown>
>(
  sources: [O1, O2, O3, O4, O5, O6]
): Observable<
  [
    ValueOfObservable<O1>,
    ValueOfObservable<O2>,
    ValueOfObservable<O3>,
    ValueOfObservable<O4>,
    ValueOfObservable<O5>,
    ValueOfObservable<O6>
  ]
>;
export function myFunction$<T>(sources: Observable<T>[]): Observable<T[]> {
  return combineLatest(sources);
}

const sub = new Subscription();

const wantedValues = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const wantedValuesAsObs$ = wantedValues.map(v => of(v));
let values: unknown[];
sub.add(myFunction$(wantedValuesAsObs$).subscribe(v => (values = v))); // error here

as you can see, myFunction$(wantedValuesAsObs$) throws the error and I do not understand why because in my eyes, the implementation itself (export function myFunction$<T>(sources: Observable<T>[]): Observable<T[]>) should handle that.

Ps: I am using combineLatest in the example just to have a functional example.

distante
  • 6,438
  • 6
  • 48
  • 90
  • `Array.map` doesn't preserve tuple lenght. When you create `wantedValuesAsObs$` you're creating an array of variable lenght, so TS doesn't know which overload, if any, is compatible with your function parameter: https://stackoverflow.com/questions/57913193/how-to-use-array-map-with-tuples-in-typescript – Roberto Zvjerković Jul 31 '21 at 07:31

1 Answers1

1

I got it working this way:

export function myFunction$<
  O1 extends Observable<unknown>,
  O2 extends Observable<unknown>,
  O3 extends Observable<unknown>,
  O4 extends Observable<unknown>,
  O5 extends Observable<unknown>,
  O6 extends Observable<unknown>
>(
  sources: [O1, O2, O3, O4, O5, O6]
): Observable<
  [
    ValueOfObservable<O1>,
    ValueOfObservable<O2>,
    ValueOfObservable<O3>,
    ValueOfObservable<O4>,
    ValueOfObservable<O5>,
    ValueOfObservable<O6>
  ]
>;
// ! Covering the case where there are more than 6 items in the array
export function myFunction$<T>(sources: Observable<T>[]): Observable<T[]>;

// The implementation is compatible with the overload signatures
export function myFunction$<T>(sources: Observable<T>[]): Observable<T[]> {
  return combineLatest(sources);
}

const sub = new Subscription();

const wantedValues = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const wantedValuesAsObs$ = wantedValues.map(v => of(v));
let values: unknown[];
sub.add(
  myFunction$(wantedValuesAsObs$).subscribe(v => ((values = v), console.log(v)))
);

Working demo.

Quoting from the docs:

The implementation signature must also be compatible with the overload signatures

And I think this was missing initially from your example.

You could have a look at combineLatest's implementation:

export function combineLatest(sources: []): Observable<never>;
export function combineLatest<A extends readonly unknown[]>(sources: readonly [...ObservableInputTuple<A>]): Observable<A>;
export function combineLatest<A extends readonly unknown[], R>(
  sources: readonly [...ObservableInputTuple<A>],
  resultSelector: (...values: A) => R,
  scheduler: SchedulerLike
): Observable<R>;

/* ... Other signatures here ... */

export function combineLatest<O extends ObservableInput<any>, R>(...args: any[]): Observable<R> | Observable<ObservedValueOf<O>[]> {
  /* ... */
} 

As you can see, the implementation signature covers all the previous cases.

Andrei Gătej
  • 11,116
  • 1
  • 14
  • 31
  • The implementation covered all overload cases, but I see that you had to add the implementation as an extra overload. Which I do not understand why is need. I really though the implementation counted as a signature. I learned something new today! – distante Aug 01 '21 at 05:56