RxJS
是 Angular
的一个重要部分. 若是不了解如何正确地使用 RxJS 处理错误,那么当错误发生时,你确定会遇到一些奇怪的问题.相反,若是你事先知道本身在作什么,你就能够消除这些奇怪的问题,并为本身节省调试的痛苦html
本文将研究git
RxJS Observables
类型
RxJS Infinite Observables
参阅 这篇文章 关于 finite Observables
和 infinite Observables
的不一样, 尽管你可能猜获得RxJS
中的错误
RxJS
中的错误,会发生什么RxJS
中的错误本文的代码能够在 github
上找到github
本文将讨论 infinite observables
- 那些你指望能从中一直获取值. 若是你错误的处理错误(do error handling wrong), 它们将再也不是infinite observables
, 而且结束 - 这将是很是糟糕的, 由于你的应用程序指望它是infinite
api
如下这些将会被研究测试
DOM Event
- 对一个在页面上键入并使用 API 查询的 keyup
的 DOM Event
进行去抖DOM Event
案例研究第一个案例研究会聚焦于处理 DOM Event
并基于它们进行搜索. 你将在两个输入框中输入《星球大战》的角色的名字. 当你中止输入 300 毫秒以后, 而且输入的内容和上一次的不相同, 将会使用 星球大战 API 搜索这些名字 而且展现. 第一个输入框会在出现错误以后继续工做,第二个输入框会在出现错误后中止工做.ui
这是界面 this
我稍微修改一下,若是你输入错误,它会搜索一个错误的 URL,从而产生一个错误url
这是相关的 HTMLspa
<input class="form-control" (keyup)="searchTerm$.next($event.target.value)" />
<input class="form-control" (keyup)="searchTermError$.next($event.target.value)" />
复制代码
keyup
事件 只是简单的使用 Subject
的 next
方法发送数据3d
这是component
代码
searchTerm$ = new Subject<string>();
searchTermError$ = new Subject<string>();
this.rxjsService
.searchBadCatch(this.searchTermError$)
.pipe(finalize(() => console.log('searchTermError$ (bad catch) finalize called!')))
.subscribe((results) => {
console.log('Got results from search (bad catch)');
this.resultsError = results.results;
});
this.rxjsService
.search(this.searchTerm$)
.pipe(finalize(() => console.log('searchTerm$ finalize called!')))
.subscribe((results) => {
console.log('Got results from search (good catch)');
this.results = results.results;
});
复制代码
这段代码基本上将向页面发送结果, 并输出日志是否被调用. 注意, 咱们调用了两个不一样的服务方法, 并传入了两个不一样的 Subject
本案例研究的错误处理代码位于 rxjsService
中:
searchBadCatch(terms: Observable<string>) {
return terms.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(term => this.searchStarWarsNames(term)),
catchError(error => {
console.log("Caught search error the wrong way!");
return of({ results: null });
})
);
}
search(terms: Observable<string>) {
return terms.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(term =>
this.searchStarWarsNames(term).pipe(
catchError(error => {
console.log("Caught search error the right way!");
return of({ results: null });
})
)
)
);
}
private searchStarWarsNames(term) {
let url = `https://swapi.co/api/people/?search=${term}`;
if (term === "error") {
url = `https://swapi.co/apix/people/?search=${term}`;
}
return this.http.get<any>(url);
}
复制代码
searchBadCatch
方法实现了糟糕的错误处理. 它看起来没有问题, 对吧? 它在 300 毫秒内去抖动,而且使用distinctUntilChanged
确保咱们不会连续两次搜索相同的东西. 在 switchMap
中, 咱们使用了searchStarWarsNames
方法,而且使用catchError
方法捕获错误. 这有什么问题吗?
若是你在 Observables 的
pipe
方法的第一层使用catchError
捕捉错误(在本例中是return terms.pipe()
),它将容许你处理错误,而且返回一个或多个结果, 可是它会紧接着终止这个observable stream
(可观察者流). 这意味着它不会再监听keyup
事件. 所以, 不管如何, 毫不容许错误渗透到这一层.
注意, 若是在Observable
的pipe
方法的第一层触达catchError
, finalize
方法将会被调用. 你能够在component
代码中看到这一点.
这里有个可视化代码(visual
), 我但愿有所帮助
永远不要让错误渗透到红线的水平.
search
方法中实现了 RxJS
错误处理的最佳实践代码
始终将
catchError
操做符 放在 相似switchMap
的方法中, 以便于它只结束 API 调用流,而后将流返回switchMap
中,继续执行Observable
. 若是你没有调用 API,确保添加了try/catch
代码来处理错误,而且不容许它渗透到第一层的pipe
, 不要假设你的代码不会失败, 必定要使用try/catch
.
因此, 你能够在代码中看到咱们在 searchStarWarsNames
方法调用中 添加了 pipe
方法,这样咱们就能够捕获错误,从而不容许错误渗透到第一层 pipe
这是最佳处理的可视化代码(visual
)
始终在 switchMap
/ mergeMap
/ concatMap
等内部的捕获错误
如今是时候看看它是如何在网页上工做的.咱们假设它一开始是工做的.当 API 调用出错时,就有乐子了.
首先,我将在两个输入框中键入错误,以下所示
我将把它做为练习, 你本身看控制台输出. 如今正式测试,我能够在处理错误后继续输入内容并得到结果吗?
这里咱们看到第一个可行,第二个再也不可行
第二个输入框出现了正如我开头介绍中所说的奇怪的问题.你将很难弄清楚为何你的搜索中止工做
NgRx Effect
案例研究我开始写这篇文章的缘由是我在一个应用程序使用 NgRx Effects
出现的奇怪的问题. 有关信息请看这里. 多是由于我没有在effect
中正确处理 RxJS 错误? 正如你在本研究中所看到的, 答案是确定的
这是界面
这里没有什么特别的
Success
- 用 person/1
(Luke Skywalker
)调用星球大战 API,并在屏幕上输出名称Error – Stops Listening
- 使用错误 URL 调用 API,所以它会生成一个错误 - catch
是错误的,因此它中止监听 effect
Error – Don’t catch error
- 使用错误的 URL 调用 API,以便生成错误 - 不捕获错误Error – Keeps Listening
- 使用错误的 URL 调用 API,以便生成错误 - 正确捕获错误,因此能够屡次单击它我会跳过 HTML,由于它只是调用组件方法的按钮. 这是组件代码
ngrxSuccess() {
this.store.dispatch(new CallWithoutError());
}
ngrxError() {
this.store.dispatch(new CallWithError());
}
ngrxErrorKeepListening() {
this.store.dispatch(new CallWithErrorKeepListening());
}
ngrxErrorDontCatch() {
this.store.dispatch(new CallWithErrorNotCaught());
}
复制代码
好(good),坏(bad)和丑陋(ugly)的错误处理都在 effect
代码中
这是咱们的成功案例
@Effect()
callWithoutError$ = this.actions$.pipe(
ofType(AppActionTypes.CallWithoutError),
switchMap(() => {
console.log("Calling api without error");
return this.http.get<any>(`https://swapi.co/api/people/1`).pipe(
map(results => results.name),
switchMap(name => of(new SetName(name))),
catchError(error => of(new SetName("Error!")))
);
}),
finalize(() => console.log("CallWithoutError finalize called!"))
);
复制代码
这个每次都会工做.即便它失败了,它会继续工做,由于 catchError
在 http.get
的 pipe
中. 在这个成功案例,SetName reducer
将向 store
添加name
, 用户界面选择并显示它.
此effect
将使用错误的 URL 调用 API,所以会生成错误. 错误处理操做不正确,所以一旦调用,这将永远不会再次运行,直到刷新应用程序.
@Effect()
callWithError$ = this.actions$.pipe(
ofType(AppActionTypes.CallWithError),
switchMap(() => {
console.log("Calling api with error - stop listening");
return this.http.get<any>(`https://swapi.co/apix/people/1`).pipe(
map(results => results.name),
switchMap(name => of(new SetName(name)))
);
}),
catchError(error => of(new SetName("Error - You're doomed!"))),
finalize(() => console.log("CallWithError finalize called!"))
);
复制代码
在这种状况下, catchError
会在 this.actions$.pipe
的第一层中别调用, 从而结束 effect
,由于它的 Observable 流将结束. 这就像上面使用 RxJS Observables 的案例研究同样. 点击后咱们应该在页面上看到Error – You’re doomed!
. 若是咱们再次尝试单击该按钮,则不会触发该effect
.
如下是此输出:
此effect
将使用错误的 URL 调用 API,所以会生成错误. 可是,它会正确处理错误,以即可以再次调用它.
@Effect()
callWithErrorKeepListening$ = this.actions$.pipe(
ofType(AppActionTypes.CallWithErrorKeepListening),
switchMap(() => {
console.log("Calling api with error - keep listening");
return this.http.get<any>(`https://swapi.co/apix/people/1`).pipe(
map(results => results.name),
switchMap(name => of(new SetName(name))),
catchError(error => of(new SetName("Error but still listening!")))
);
}),
finalize(() => console.log("CallWithErrorKeepListening finalize called!"))
);
复制代码
处理 RxJS 错误的正确方法是将 catchError
放在 http.get
的pipe
中. 它将结束 http.get
的 observable
,但这并不重要,由于它不管如何都是finite observable
,只发出一个值. 当它返回SetName action
时,switchMap
将emit
并继续 Observable 流. 请注意,此处的finalize
将永远不会被调用.
如下是此输出:
这是咱们的最后一个effect
, 并回答了咱们的问题"若是咱们没有 catch 错误会发生什么?" 这个问题的答案是它的行为与咱们不正确地处理错误的行为相同(由于这就是 RxJS 如何运转). 只是你没有处理(hooking into)那个错误流.
@Effect()
callWithErrorDontCatch$ = this.actions$.pipe(
ofType(AppActionTypes.CallWithErrorNotCaught),
switchMap(() => {
console.log("Calling api with error - don't catch");
return this.http.get<any>(`https://swapi.co/apix/people/1`).pipe(
map(results => results.name),
switchMap(name => of(new SetName(name)))
);
}),
finalize(() => console.log("CallWithErrorNotCaught finalize called!"))
);
复制代码
此外,因为你没有 在 catchError
中调用 SetName
, 所以不会在 UI 上设置 name. 所以,若是点击第一个按钮,将看不到任何输出,或者将看到上一次设置的 name. 另外一个很难调试的“怪异问题”.
正如你在本文中所知,知道如何在 Angular 应用程序中正确处理的 RxJS 错误将帮助你阻止 infinite Observable
意外结束的奇怪的问题. 利用这些知识,你应该可以确保你的infinite Observables
永远不会结束,直到你决定结束它们为止.
文章如有纰漏请你们补充指正,谢谢~~
blog.xinshangshangxin.com SHANG 殇