RxJSのマップ演算子(angular)からエラーを投げる方法

observable'のmap演算子から、ある条件に基づいてエラーを投げたい。例えば、正しいAPIデータを受信しなかった場合などです。以下のコードを参照してください:

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.") );
}

基本的には、コードを見てわかるように、レスポンス(resオブジェクト)が 'bearerToken'を持っていない場合、エラーを投げたいのです。私のサブスクリプションでは、以下の第2パラメータ(handleError)に入ります。

.subscribe(success, handleError)

何か提案はありますか?

質問へのコメント (10)
ソリューション

エラーを map() 演算子の中で投げるだけです。RxJSのすべてのコールバックはtry-catchブロックでラップされているので、エラーはキャッチされ、error通知として送信されます。

つまり、何も返さず、ただエラーを投げるだけです:

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

throwError()(RxJS 5では旧Observable.throw())はObservableであり、error通知を送信するだけであるが、map()は何を返すかは気にしない。たとえmap()から Observable を返したとしても、それはnext` 通知として渡されます。

最後に、おそらく .catchError() (RxJS 5では catch()) を使う必要はないでしょう。エラーが発生したときに何らかの副作用を実行する必要がある場合は、例えば tap(null, err => console.log(err)) (RxJS 5の旧do()) を使う方が良いでしょう。

2019年1月: RxJS 6用に更新しました。

解説 (7)

もしthrow new Error()が観測不可能に思えるなら、switchMapを使うことができる:

// 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.httpPost.pipe(switchMap(res => (res.bearerToken) ? 
                                    of(this.saveJwt(res.bearerToken)) : 
                                    throwError('Valid token not returned')
));

文法が違うだけで、動作は同じです。

文字通り、パイプ内のhttp observableから別のobservableに'switch'と言っているのです。

of`をつけるのを忘れないようにしてください。

また、'switchMap'の良いところは、saveJwtでどんなロジックを実行する必要があっても、全く新しい'コマンドのチェーンを返すことができることです。

解説 (1)

この質問にはすでに答えが出されていますが、私なりのアプローチを紹介したいと思います(上記とは若干異なりますが)。

私はマッピングとは別に何を返すかを決定します。どの演算子が最適か分からないので、tapを使うことにする。

this.httpPost.pipe(
  tap(res => { 
    if (!res.bearerToken) {
      throw new Error('Valid token not returned');
    }
  }),
  map(res => this.saveJwt(res.bearerToken)),
);
解説 (3)