[譯] 除錯 RxJS 第2部分: 日誌篇

SangKa發表於2017-11-16

原文連結: blog.angularindepth.com/debugging-r…
本文為 RxJS 中文社群 翻譯文章,如需轉載,請註明出處,謝謝合作!
如果你也想和我們一起,翻譯更多優質的 RxJS 文章以奉獻給大家,請點選【這裡】

日誌沒什麼可興奮的。

然而,日誌是獲取足夠資訊以開始推斷問題的直接方式,它不是靠猜的,而且它通常用於除錯 RxJS 程式碼。

本文是除錯 RxJS 系列文章的第二篇,繼 除錯 RxJS 第1部分: 工具篇之後,側重於使用日誌來解決實際問題。在本文中,我將展示如何以一種不唐突的方式來使用 rxjs-spy 獲取詳情和有針對性的資訊。

來看一個簡單示例,示例中使用的是 rxjsrxjs-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 的 nextcomplete 通知,日誌輸出還包括了訂閱和取消訂閱的通知。它顯示了所發生的一切:

  • 訂閱組合 observable 會並行訂閱每個使用者 API 請求的 observable
  • 請求完成的順序是不固定的
  • observables 全部完成
  • 全部完成後,組合 observable 的訂閱會自動取消訂閱

每個日誌中的通知都包含接收該通知的訂閱者 ( Subscriber )的資訊,其中包括訂閱者訂閱的數量和 subscribe 呼叫的堆疊跟蹤:

堆疊跟蹤指向的是根源的 subscribe 呼叫,也就是 observable 訂閱者的顯式訂閱。所以,使用者請求 observables 的堆疊跟蹤也指向 medium.js (譯者注: 即上面的程式碼檔案) 中的 subscribe 呼叫:

當除錯時,我發現知道實際的 subscribe 呼叫地點比知道位於組合 observable 中間的 subscribe 呼叫地點更有用。

現在我們來看一個現實問題。

當編寫 redux-observable 的 epics 或 ngrxeffects 時,我見過一些開發者的程式碼大概是這樣的:

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 也就完成了。

解決方法是將 mapcatch 的呼叫移到 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/usersgithub/repos 這樣的複合標籤就可以讓所有標記名以 github 開頭的 observables 啟用日誌。

日誌沒什麼可興奮的,但是從日誌的輸出中收集到的資訊通常可以節省大量的時間。採用靈活的標記方法可以進一步減少處理日誌相關程式碼的時間。

相關文章