原文:Debugging RxJS, Part 2: Logging
譯者:Ice Panpan;校驗者:暫無
日誌記錄並不是一件讓人興奮的事。
然而,這是獲得足夠的資訊來推理問題最直接的方法,而不需要去猜測。它通常是除錯 RxJS
程式碼的首選方法。這是這個系列文章的第二篇,專注於使用日誌記錄來解決實際問題。在第一篇除錯 Rxjs(一):工具中,主要介紹的是 rxjs-spy
。在本文中,我將展示如何使用 rxjs-spy
以最小的影響來獲取詳細並有針對性的資訊。
讓我們看一個使用 rxjs
和 rxjs-spy
UMD捆綁的簡單案例:
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
操作符來標記 Observable
,並且僅僅通過字串來給 Observable
註釋。這個示例在組合 Observable
之前,首先啟用監聽功能,並配置了哪些 Observable
要被記錄——匹配 /user-.+/
的正規表示式或者帶有 users
標籤的那些 Observable
。
這個示例的控制檯輸出如下:
除了 Observable
的 next
和 complete
的通知之外,記錄的輸出還包括訂閱和取消訂閱的通知。它顯示了發生的一切:
- 對組合的
Observable
的訂閱影響的每一個使用者API請求的Observable
的並行訂閱; - 請求以任意順序完成;
Observable
全部完成;- 並且在全部完成後取消對組合
Observable
的訂閱。
每個記錄的通知還包括有關接受通知的訂閱者的資訊——包括訂閱者具有的訂閱量以及 subscribe
呼叫的堆疊痕跡:
堆疊痕跡指的是 subscribe
呼叫的根——即影響訂閱者對 Observable
訂閱的顯式呼叫。因此,使用者請求的 Observable
的堆疊痕跡也參考了 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。
問題是,它有時會停止工作,特別是在一個錯誤的動作發生之後。
記錄顯示正在發生的事:
在錯誤的動作被髮射出去之後,看到 redux-observalbe
基礎結構從epic中解除訂閱的 Observable
完成了。該文件的 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將不再完成,並繼續傳送錯誤動作:
在這兩個示例中,需要對正在除錯的程式碼進行的唯一修改是新增了一些標記註釋。
註釋的影響很小,一旦新增,我傾向於將它們留在程式碼中。標籤運算子可以獨立於診斷 rxjs-spy
使用——使用rxjs-spy/add/operator/tag
或直接匯入 rxjs-spy/operator/tag
,因此保留標記的開銷很小。
可以使用正規表示式配置記錄器,這可以產生許多可能的標記方法。例如,使用複合標記,例如 github/users
和 github/repos
將允許您為所有被標記後儲存在儲存庫裡的 github Observable
啟用日誌記錄。
記錄並不令人興奮,但可以從記錄的輸出中收集的資訊通常可以節省大量時間。採用靈活的標記方法可以進一步減少處理與日誌記錄相關的程式碼所花費的時間。