The original link: blog.angularindepth.com/debugging-r… This article is translated by RxJS Chinese community, if you need to reprint, please indicate the source, thank you for your cooperation! If you would like to translate more quality RxJS articles with us, please click here.

Logs are nothing to get excited about.

However, logging is a straightforward way to get enough information to start inferencing problems, it’s not guesswork, and it’s often used to debug RxJS code.

This article, the second in a series of debugging RxJS articles, focuses on using logs to solve real-world problems, following debugging RxJS Part 1: Tools. In this article, I’ll show you how to use RxJS-Spy in an unobtrusive way to get detailed and targeted information.

Take a look at a simple example using RXJS and RxJS-Spy’s UMD bundles:

RxSpy.spy();
RxSpy.log(/user-.+/);
RxSpy.log('users');

const names = ['benlesh'.'kwonoj'.'staltz'];
constusers = Rx.Observable.forkJoin(... names.map(name= >
  Rx.Observable
    .ajax
    .getJSON(`https://api.github.com/users/${name}`)
    .tag(`user-${name}`)
))
.tag('users');

users.subscribe();Copy the code

ForkJoin is used in the example to form an Observable that emits an array of GitHub users.

Rxjs-spy works on observables tagged with the Tag operator, which annotates observables with string tag names, and nothing more. Before forming an Observable, the example enables reconnaissance and configures the logger for an Observable that matches /user-.+/ regular expressions or tags named Users.

The input for the example should look like this:

In addition to observable’s Next and complete notifications, the log output includes subscribe and unsubscribe notifications. It shows what happened:

  • The subscription composite Observable subscribes to each observable requested by the user API in parallel
  • The order in which requests are completed is fluid
  • Observables are all completed
  • When all is done, the combined Observable subscription is automatically unsubscribed

Each notification in the log contains information about the Subscriber that received the notification, including the number of subscriptions and the stack trace of subscribe calls:

The stack trace points to the root subscribe call, which is the explicit subscription of the Observable subscriber. So, the stack trace of the user’s request for observables also points to the subscribe call in media.js:

When debugging, I find it more useful to know the actual SUBSCRIBE call location than the SUBSCRIBE call location located in the middle of the combined Observable.

Now let’s look at a real problem.

When writing epics for Redux-Observable or Effects for NGRX, I’ve seen developers write something like this:

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');Copy the code

It looks fine at first glance, and for the most part it works fine. This bug is not found in unit tests.

The problem is that sometimes Epic stops running. More specifically, when Dispatch sends an action that reported an error, it stops running.

The log shows exactly what happened:

After an error action is issued, the Observable completes because the Redux-Observable infrastructure unsubscribes to Epic. The documentation for the catch operator explains why this happens:

No matterselectorWhatever observable the function returns is used to continue executing the Observable chain.

In Epic, the Observable returned by the catch is done, and epic is done.

The solution is to move the map and catch calls to the switchMap, like this:

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');Copy the code

Then epic will not finish, and it will continue to dispatch the actions that reported the error:

In both cases, the only change required for the code being debugged was the addition of some tag annotation.

Comments are lightweight and only need to be added once, and I prefer to leave them in the code. The use of tag operators can be independent of the diagnostic functionality in rxjS-Spy, either by using rxjs-spy/add/operator/tag or by importing them directly from rxjs-Spy /operator/tag. So the cost of keeping the tag is small.

Loggers can be configured using regular expressions, which leads to a variety of possible tags. For example, using compound tags such as github/ Users and github/repos enables logging for all observables whose tag names start with Github.

Logging is nothing to get excited about, but the information gleaned from the output of logging can often save a lot of time. A flexible markup approach can further reduce the time spent working with log-related code.