Was möchte Typescript hier gerne? (RxJs, defaultIfEmpty)

Hendoul

Commander
Registriert
Apr. 2008
Beiträge
2.049
Hi :)

https://stackblitz.com/edit/rxjs-defaultifempty13?devtoolsheight=60&file=index.ts

Ich verstehe nicht, warum TS hier nicht mehr zufrieden ist. Ich habe von RxJs von 6.5 auf 7.5 geupdated.
Ich habe gesehen, dass die irgendwas bei der Type inference bei defaultIfEmpty geändert/gefixt haben:
https://github.com/ReactiveX/rxjs/blob/master/CHANGELOG.md
  • TS: fix type inference for defaultIfEmpty. (#4833) (9b5ce2f)
https://github.com/reactivex/rxjs/commit/9b5ce2f

Die Signatur von defaultIfEmpty sieht jetzt so aus:
export function defaultIfEmpty<T, R = T>(defaultValue?: R): OperatorFunction<T, T | R>;
Was bedeutet überhaupt das R=T?

Mein code hier:return of({ valueA: null, valueB: null });

Da müsste doch die Type inference funktionieren und das als ISomething[] erkennen?
Zuerst dachte ich die null sind das Problem, aber es ist egal, ob ich da null oder 1 reinschreibe.

Dann dachte ich das leere Array hier könnte noch ein Problem sein, da untypisiert?
return forkJoin(something).pipe(defaultIfEmpty([]));


Die Fehlermeldung:
Type 'Observable<any[] | (ISomething[] | { valueA: any; valueB: any; })[]>' is not assignable to type 'Observable<ISomething[]>'.
Kann ich irgendwie noch nachvollziehen, denke ich zumindest... Er hat jetzt nicht nur ein ein Observable<ISomething[]> sondern wegen dem return im catchError auch noch ein { valueA: any; valueB: any; } und wegen dem defaultIfEmpty([]) noch das any[] ?

Wie kann ich denn das nun lösen, ohne im Rückgabetyp von fetchSomething auf ein any[] zurückgreifen zu müssen?
 
Ich glaub dein Problem liegt nur darin, dass deine Methoden falsch typisiert sind. Du kommst mit den Typen von ISomething und ISomething[] durcheinander.
Was genau returned die Function getPerformance? ein einzelnes ISomething, oder ein Array von ISomething verpackt als Observable?
Was soll die fetchSomething Function am Ende returnen? Observable<ISomething[]> oder Observable<ISomething[][]> ?

Falls getPerformance in wirklichkeit nur ein einzelnes ISomething returned:
Javascript:
import './style.css';

import { of, Observable, forkJoin } from 'rxjs';
import { catchError, defaultIfEmpty, pluck } from 'rxjs/operators';

interface IResponse<T> {
    data: T;
}

interface ISomething {
    valueA: number;
    valueB: number;
}

class Something {
  fetchSomething(ids: string[]): Observable<ISomething[]> {
    const something = ids.map(id => {
      return this.getPerformance(id).pipe(
        this.extractResponseData(),
        catchError(error => {
          return of({ valueA: null, valueB: null });
        })
      );
    });
 
    return forkJoin(something).pipe(defaultIfEmpty([]));
  }

  getPerformance(portfolioId: string): Observable<IResponse<ISomething>> {
        return undefined; // doesn't matter for test purpose
    }

  extractResponseData() {
    return <T>(source: Observable<IResponse<T>>) => {
      return source.pipe(pluck('data'));
    };
  }
}


Falls getPerformance wirklich Observable<ISomething[]> returned, musst du im of(...) innerhalb des catchErrors auch ein Array zurückgeben und den returntype von fetchSomething entsprechend zum Array von Arrays machen:
Javascript:
import './style.css';

import { of, Observable, forkJoin } from 'rxjs';
import { catchError, defaultIfEmpty, pluck } from 'rxjs/operators';

interface IResponse<T> {
    data: T;
}

interface ISomething {
    valueA: number;
    valueB: number;
}

class Something {
  fetchSomething(ids: string[]): Observable<ISomething[][]> {
    const something = ids.map(id => {
      return this.getPerformance(id).pipe(
        this.extractResponseData(),
        catchError(error => {
          return of([{ valueA: null, valueB: null }]);
        })
      );
    });
 
    return forkJoin(something).pipe(defaultIfEmpty([]));
  }

  getPerformance(portfolioId: string): Observable<IResponse<ISomething[]>> {
        return undefined; // doesn't matter for test purpose
    }

  extractResponseData() {
    return <T>(source: Observable<IResponse<T>>) => {
      return source.pipe(pluck('data'));
    };
  }
}

Für den Fall, dass dein getPerformance wirklich ein Array returned, du am ende von fetchSomething aber auch nur ein Array haben willst, musst du die Daten irgendwie flatten. Also z.b. mit concatAll oder mergeAll
 
Zuletzt bearbeitet:
Der Stackblitz-Link funktioniert nicht, aber ich betrachte mal den von Nafi geposteten Code.

1. Wie Nafi schon gesagt hat, stimmt da was bezüglich Array nicht.
2. { valueA: null, valueB: null } geht nicht als ISomething durch, weil valueA und valueB number und nicht number | null sind.
3. Vermutlich reicht Type-Inference auch nicht aus und allgemein ist es in so einem Fall eh besser, die Type-Parameter explizit anzugeben, um bessere Fehlermeldungen zu erhalten. Also of<ISomething>({ valueA: 0, valueB: 0 }) und defaultIfEmpty<ISomething[]>([]). Bei ersterem hätte TypeScript sich sofort beschwert, dass das mit null kein ISomething ist.

R = T bedeutet übrigens, dass der zweite Type-Parameter optional ist, mit T als default.

Edit: nochmal die Fehlermeldung genauer angeschaut. "of" benötigt wohl kein Array. Aber irgendwo kommt da noch ein ISomething[][] her.
 
Zuletzt bearbeitet:
Ok, es ist sicher kein zweidimensionales Array. Das hat mich dann auf die Spur gebracht, dass getPerformance nur ein einzelnes Element zurückliefert und kein Array.

Vielen Dank für die Hilfe :)
 
Zurück
Oben