Jak zgłosić błąd z operatora mapy RxJS (kątowy)

93

Chcę zgłosić błąd z operatora mapy mojego obserwowalnego na podstawie warunku. Na przykład, jeśli nie otrzymano poprawnych danych API. Zobacz poniższy kod:

private userAuthenticate( email: string, password: string ) {
    return this.httpPost(`${this.baseApiUrl}/auth?format=json&provider=login`, {userName: email, password: password})
        .map( res => { 
            if ( res.bearerToken ) {
                return this.saveJwt(res.bearerToken); 
            } else {
                // THIS DOESN'T THROW ERROR --------------------
                return Observable.throw('Valid token not returned');
            }
        })
        .catch( err => Observable.throw(this.logError(err) )
        .finally( () => console.log("Authentication done.") );
}

Zasadniczo, jak widać w kodzie, jeśli odpowiedź (obiekt res) nie ma „bearerToken”, chcę wyrzucić błąd. Więc w mojej subskrypcji trafia do drugiego parametru (handleError) wspomnianego poniżej.

.subscribe(success, handleError)

Jakieś sugestie?

Hassan
źródło
4
O co chodzi throw 'Valid token not returned';?
Günter Zöchbauer
Nieudana kompilacja
Hassan
Dokładny komunikat o błędzie.
Günter Zöchbauer
2
Przepraszam, nie działa, return throw 'message here'ale działa bez returnsłowa kluczowego. Sprawdzę, czy działa poprawnie logicznie.
Hassan
Tekst błędu nie jest odbierany w subscribemetodzie, a .finally()strumień w strumieniu również jest wyzwalany. (Jednak egzekucja zostaje zatrzymana, co jest dobre)
Hassan

Odpowiedzi:

141

Po prostu wrzuć błąd do wnętrza map()operatora. Wszystkie wywołania zwrotne w RxJS są opakowane blokami try-catch, więc zostaną przechwycone, a następnie wysłane jako errorpowiadomienie.

Oznacza to, że nic nie zwracasz i po prostu wyrzucasz błąd:

map(res => { 
  if (res.bearerToken) {
    return this.saveJwt(res.bearerToken); 
  } else {
    throw new Error('Valid token not returned');
  }
})

throwError()(Dawniej Observable.throw()w RxJS 5) jest zauważalny, które po prostu wysyła errorpowiadomienie, ale map()nie obchodzi mnie to, czego wracać. Nawet jeśli zwrócisz z map()niego Observable , zostanie przekazane jako nextpowiadomienie.

Ostatnia rzecz, prawdopodobnie nie musisz jej używać .catchError()(poprzednio catch()w RxJS 5). Jeśli potrzebujesz wykonać jakieś efekty uboczne, gdy wystąpi błąd, lepiej użyć na przykład tap(null, err => console.log(err))(dawniej do()w RxJS 5).

Sty 2019: Zaktualizowano dla RxJS 6

jaskółka oknówka
źródło
1
Dzięki @martin - Tak, Twoje rozwiązanie działa. W rzeczywistości miałem również problem w mojej metodzie logError, na co zwrócił uwagę @ GünterZöchbauer. Miałem do returnobiektu błąd z niego i teraz działa idealnie :) Dzięki!
Hassan
@martin: Czy mógłbyś wyjaśnić, dlaczego nie chcielibyśmy tutaj .catch ()?
Bob
1
@Bob Ponieważ OP używał catch()tylko do logowania i ponownego zgłaszania błędu, co jest niepotrzebne, jeśli chcesz tylko wykonać efekt uboczny (rejestrowanie błędu) i jest łatwiejsze w użyciu po prostudo()
martin
1
Czy to jest identyczne z return throwError(new Error('Valid token not returned'));?
Simon_Weaver,
@Simon_Weaver nie, nie jest. return throwError()zwraca an Observable<never>, to po prostu natychmiast przerywa obserwowalny strumień, bez żadnego powrotu.
Przywróć Monikę
25

Jeśli throw new Error()wydaje ci się, że wydaje się nieobserwowalne - tak jak możesz użyć throwError(...)z switchMapzamiast map(różnica polega na tym, że switchMapzwraca nową obserwowalną):

// this is the import needed for throwError()
import { throwError } from 'rxjs';


// RxJS 6+ syntax
this.httpPost.pipe(switchMap(res => { 
   if (res.bearerToken) {
      return of(this.saveJwt(res.bearerToken)); 
   } 
   else {
      return throwError('Valid token not returned');  // this is 
   }
});

lub bardziej zwięźle:

this.httpPost.pipe(switchMap(res => (res.bearerToken) ? 
                                    of(this.saveJwt(res.bearerToken)) : 
                                    throwError('Valid token not returned')
));

Zachowanie będzie takie samo, to po prostu inna składnia.

Dosłownie mówisz „przełącz” z http obserwowalnego w potoku na inną obserwowalną, która jest albo po prostu „zawijaniem” wartości wyjściowej, albo nowym obserwowalnym „błędem”.

Nie zapomnij wstawić of, bo otrzymasz mylące komunikaty o błędach.

Piękno „switchMap” polega również na tym, że jeśli chcesz, możesz zwrócić cały nowy „łańcuch” poleceń - bez względu na to, z jaką logiką trzeba się uporać saveJwt.

Simon_Weaver
źródło
4
Kiedyś zacząłem myśleć switchMapo asynchronicznej ifwypowiedzi - rzeczy miały dużo więcej sensu :-)
Simon_Weaver
3

Chociaż na to pytanie już udzielono odpowiedzi, chciałbym podzielić się moim podejściem (chociaż różni się ono tylko nieznacznie od powyższego).

Zdecydowałbym, co jest zwracane niezależnie od mapowania i na odwrót. Nie jestem pewien, który operator jest najlepszy do tego, więc użyję tap.

this.httpPost.pipe(
  tap(res => { 
    if (!res.bearerToken) {
      throw new Error('Valid token not returned');
    }
  }),
  map(res => this.saveJwt(res.bearerToken)),
);
christo8989
źródło
zwracana wartość tapjest ignorowana. ten kod działa inaczej niż mówi
sf
Nadal przyzwyczajam się do rxjs. Czy użycie switchMap byłoby lepsze? Czy ktoś może zasugerować innego operatora lub bezpośrednio edytować?
christo8989
Myślę, że ta sugerowana throw new Error()opcja jest jak dotąd najlepsza
sf