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 殤