原文連結: blog.angularindepth.com/debugging-r…
本文為 RxJS 中文社群 翻譯文章,如需轉載,請註明出處,謝謝合作!
如果你也想和我們一起,翻譯更多優質的 RxJS 文章以奉獻給大家,請點選【這裡】
日誌沒什麼可興奮的。
然而,日誌是獲取足夠資訊以開始推斷問題的直接方式,它不是靠猜的,而且它通常用於除錯 RxJS 程式碼。
本文是除錯 RxJS 系列文章的第二篇,繼 除錯 RxJS 第1部分: 工具篇之後,側重於使用日誌來解決實際問題。在本文中,我將展示如何以一種不唐突的方式來使用 rxjs-spy
獲取詳情和有針對性的資訊。
來看一個簡單示例,示例中使用的是 rxjs
和 rxjs-spy
的 UMD bundles:
RxSpy.spy();
RxSpy.log(/user-.+/);
RxSpy.log('users');
const names = ['benlesh', 'kwonoj', 'staltz'];
const users = Rx.Observable.forkJoin(...names.map(name =>
Rx.Observable
.ajax
.getJSON(`https://api.github.com/users/${name}`)
.tag(`user-${name}`)
))
.tag('users');
users.subscribe();複製程式碼
示例中使用 forkJoin
來組成一個發出 GitHub 使用者陣列的 observable 。
rxjs-spy
對使用 tag
操作符標記過的 observables 起作用,tag
操作符使用字串標籤名來註釋 observable,僅此而已。在組成 observable 之前,示例啟用了偵察,併為匹配 /user-.+/
正規表示式或標籤名為 users
的 observable 配置日誌記錄器。
示例的輸入看上去應該是這樣的:
除了 observable 的 next
和 complete
通知,日誌輸出還包括了訂閱和取消訂閱的通知。它顯示了所發生的一切:
- 訂閱組合 observable 會並行訂閱每個使用者 API 請求的 observable
- 請求完成的順序是不固定的
- observables 全部完成
- 全部完成後,組合 observable 的訂閱會自動取消訂閱
每個日誌中的通知都包含接收該通知的訂閱者 ( Subscriber )的資訊,其中包括訂閱者訂閱的數量和 subscribe
呼叫的堆疊跟蹤:
堆疊跟蹤指向的是根源的 subscribe
呼叫,也就是 observable 訂閱者的顯式訂閱。所以,使用者請求 observables 的堆疊跟蹤也指向 medium.js
(譯者注: 即上面的程式碼檔案) 中的 subscribe
呼叫:
當除錯時,我發現知道實際的 subscribe
呼叫地點比知道位於組合 observable 中間的 subscribe
呼叫地點更有用。
現在我們來看一個現實問題。
當編寫 redux-observable
的 epics 或 ngrx
的 effects 時,我見過一些開發者的程式碼大概是這樣的:
import { Observable } from 'rxjs/Observable';
import { ajax } from 'rxjs/observable/dom/ajax';
const getRepos = action$ =>
action$.ofType('REPOS_REQUEST')
.map(action => action.payload.user)
.switchMap(user => ajax.getJSON(`https://api.notgithub.com/users/${user}/repos`))
.map(repos => { type: 'REPOS_RESPONSE', payload: { repos } })
.catch(error => Observable.of({ type: 'REPOS_ERROR' }))
.tag('getRepos');複製程式碼
乍看上去沒什麼問題,而且大多數情況下也能正常執行。這種 bug 還是在單元測試裡發現不了的。
問題就是有時候 epic 就會停止執行。再具體一點就是當 dispatch 了報錯的 action 後它會停止執行。
日誌顯示了具體發生了什麼:
發出報錯的 action 後, observable 便完成了,因為 redux-observable
的基礎設施取消了 epic 的訂閱。catch
操作符的文件解釋了這一現象發生的原因:
無論
selector
函式返回的 observable 是什麼,都會被用來繼續執行 observable 鏈。
在 epic 中,catch
返回的 observable 完成了,epic 也就完成了。
解決方法是將 map
和 catch
的呼叫移到 switchMap
裡面,就像這樣:
import { Observable } from 'rxjs/Observable';
import { ajax } from 'rxjs/observable/dom/ajax';
const getRepos = action$ =>
action$.ofType('REPOS_REQUEST')
.map(action => action.payload.user)
.switchMap(user => ajax
.getJSON(`https://api.notgithub.com/users/${user}/repos`)
.map(repos => { type: 'REPOS_RESPONSE', payload: { repos } })
.catch(error => Observable.of({ type: 'REPOS_ERROR' }))
)
.tag('getRepos');複製程式碼
這樣 epic 便不會完成,它會繼續 dispatch 報錯的 actions:
在這兩個示例中,對於被除錯的程式碼來說,唯一需要修改就是是新增了某個標記註釋。
註釋是輕量級的,只需新增一次,我傾向於將它們留在程式碼中。tag
操作符的使用可以獨立於 rxjs-spy
中診斷功能,通過使用 rxjs-spy/add/operator/tag
或直接從 rxjs-spy/operator/tag
匯入。所以保留標籤的成本很小。
日誌記錄器可以使用正規表示式來配置,這會導致了多種可能性的標記。例如,使用像 github/users
和 github/repos
這樣的複合標籤就可以讓所有標記名以 github
開頭的 observables 啟用日誌。
日誌沒什麼可興奮的,但是從日誌的輸出中收集到的資訊通常可以節省大量的時間。採用靈活的標記方法可以進一步減少處理日誌相關程式碼的時間。