如何在 RxJS 的 map 操作符中抛出错误(Angular)

139

我希望在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,我想抛出一个错误。这样,在我的订阅中,它将进入下面提到的第二个参数(handleError)。
.subscribe(success, handleError)

有任何建议吗?


4
throw 'Valid token not returned';的意思是“抛出‘未返回有效令牌’的异常”。 - Günter Zöchbauer
2
哦,抱歉,它不支持使用return throw 'message here',但是不使用return关键字可以正常工作。让我检查一下逻辑是否正确。 - Hassan
1
错误文本未在“subscribe”方法中接收,而流中的“.finally()”也会触发。(但执行已停止,这是一件好事) - Hassan
这就是为什么 subscribe() 没有接收到任何内容。this.logError(err) 应该返回 err;subscribe(...) 接收到了 Observable.throw(...) 发出的内容。如果它发出的值是 null,那么 subscribe(...) 将接收到 null - Günter Zöchbauer
@GünterZöchbauer 先生,您真是个天才! :) 它现在传递错误消息了。然而最后的'.finally'方法仍在运行,这是设计上的吗? - Hassan
显示剩余5条评论
4个回答

196

只需在map()操作符内部抛出错误。RxJS中的所有回调都被封装在try-catch块中,因此它将被捕获并作为error通知发送。

这意味着您不返回任何内容,只需抛出错误:

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

throwError()是一个Observable,它只发送一个error通知,但map()不关心你返回什么。即使你从map()返回一个Observable,它也会被作为next通知传递。

最后一件事,你可能不需要使用.catchError()(RxJS 5中的catch())。如果你需要在发生错误时执行任何副作用,则最好使用tap(null,err => console.log(err))(例如,RxJS 5中的do())。

2019年1月:更新为RxJS 6


1
谢谢@martin - 是的,你的解决方案有效。实际上我的logError方法也有问题,@GünterZöchbauer指出了这一点。我必须从中return错误对象,现在它完美地工作了:) 谢谢! - Hassan
@martin:您能否详细说明为什么我们不想在这里使用 .catch()? - Bob
1
@Bob 因为原帖作者只是使用 catch() 来记录并重新抛出错误,如果你只想执行副作用(记录错误),那么这是不必要的,并且只使用 do() 更容易。 - martin
1
这与 return throwError(new Error('Valid token not returned')); 完全相同吗? - Simon_Weaver
1
@Simon_Weaver 不是这样的。return throwError() 返回一个 Observable<never>,这只是立即中断可观察流,而不返回任何内容。 - RaidenF
显示剩余2条评论

51

如果你觉得 throw new Error() 似乎不够明显,你可以使用 switchMap 替换 map 并使用 return throwError(...)(区别在于 switchMap 必须返回一个新的可观察对象而不是一个原始值):

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

行为将保持不变,只是语法不同。

你的意思是从HTTP可观察对象中的管道“切换”到另一个可观察对象,这个可观察对象要么只是“包装”输出值,要么是一个新的“错误”可观察对象。

不要忘记加上of,否则会得到一些令人困惑的错误消息。

'switchMap' 的美妙之处在于,如果需要进行与 saveJwt 相关的逻辑处理,可以返回一个全新的“命令链”。


8
当我开始把 switchMap 看作是一个异步的 if 语句时,很多事情就更加清晰明了 :-) - Simon_Weaver
如果你遇到了 Importing this module is blacklisted. Try importing a submodule instead. (import-blacklist)tslint(1),那么请使用 import { throwError } from 'rxjs/internal/observable/throwError'; - Cameron Hudson
2
提醒一下,从 RxJS 8 开始,throwError(...) 的参数如果是原始类型(例如错误消息),将被弃用。相反,请传递一个错误工厂函数,即 throwError(() => new Error(...)) - marked-down

8
尽管这个问题已经有了答案,我还是想分享一下我的方法(尽管与上面的方法略有不同)。我会将返回值与映射分开处理。我不确定哪种操作符最适合这个任务,所以我会使用tap
this.httpPost.pipe(
  tap(res => { 
    if (!res.bearerToken) {
      throw new Error('Valid token not returned');
    }
  }),
  map(res => this.saveJwt(res.bearerToken)),
);

1
tap的返回值被忽略了。这段代码做的事情与它所描述的不同。 - s-f
我还在逐渐熟悉rxjs。使用switchMap会更好吗?有人能建议一个不同的操作符或直接编辑吗? - christo8989
我认为建议的 throw new Error() 是到目前为止最好的选择。 - s-f
2
我最初对这种方法持怀疑态度,但是rxjs文档有类似的例子 https://rxjs.dev/api/operators/tap#example-2 - Petri Ryhänen

-1

2
欢迎提供解决方案的链接,但请确保您的答案即使没有链接也是有用的:在链接周围添加上下文,以便其他用户了解它的内容和原因,然后引用您链接的页面中最相关的部分,以防目标页面无法访问。仅仅提供链接的答案可能会被删除。 - 4b0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接