Debugging an Observable Stream

This inner observable keeps things simple when it comes to managing the split data, but it creates a bit of a mess in the middle of our previously-debuggable observable flow. Debugging was easy when there was only one observable—just add .subscribe(console.log) at the point of contention and comment out the rest. Now there are mutliple observable chains kicking around (and one of them doesn’t even have an explicit subscribe). How can we peek into a running observable chain to see what’s going on?

Enter the tap operator. This operator does not modify any of the in-flight data or observables surounding it. Instead, it allows us to peek into what’s going on inside the stream, observing the data, but not manipulating it. This allows for debugging by logging or adding in other side effects (say, tracking application performance). Here, each value is logged twice, once from tap and once from subscribe. Run this yourself to confirm that tap is not manipulating the data, just passing it on:

 interval(1000)
 .pipe(
  tap(val => {
  console.log(​'inside tap'​, val);
 // This return doesn't change the final value
 return​ val * 100;
  })
 )
 .subscribe(val => console.log(​'inside subscribe'​, val));
images/aside-icons/note.png

In previous versions of RxJS, the tap operator was known as do. This changed in version 6 because do is a reserved word in JavaScript. Now that operators are imported as separate variables, it wouldn’t do to have do as a variable. Instead, tap was used (and is more descriptive of what the operator does).

Here’s the final version of the Pig Latin mergeMap example. Try adding a tap or two to inspect things at different points in the stream. Are the values what you’d expect?

 import​ { fromEvent, ​from​ } ​from​ ​'rxjs'​;
 import​ { map, mergeMap, reduce } ​from​ ​'rxjs/operators'​;
 
 // Converts a word in English to one in Pig Latin
 function​ pigLatinify(word) {
 // Handle single-letter case and empty strings
 if​ (word.length < 2) {
 return​ word;
  }
 return​ word.slice(1) + ​'-'​ + word[0] + ​'ay'​;
 }
 
 fromEvent<any>(textbox, ​'keyup'​)
 .pipe(
  map(event => event.target.value),
  mergeMap(wordString =>
 // Inner observable
 from​(wordString.split(​/​​s​​+/​))
  .pipe(
  map(pigLatinify),
  reduce((bigString, newWord) => bigString + ​' '​ + newWord, ​''​)
  )
  )
 )
 .subscribe(translatedWords => results.innerText = translatedWords);

In the case of Pig Latin, the array passed to the inner observable contains nothing more complicated than strings. We could get away without a mergeMap here. On the other hand, your observable chain might contain more complicated objects (as you’ll see with the typeahead example). Using mergeMap to extract the contents of the array simplifies the operators that follow at the cost of adding a small amount of complexity in the form of an inner observable. As a general rule, if the contents of the array are complex (nested objects), or the per-item processing is complex (for instance, if there’s async processing), it’s best to use the mergeMap technique. Otherwise, the trade-off of added complexity through the inner observable isn’t worth it.

Another debugging technique is the toArray operator, a specialized version of reduce. toArray waits for the stream to complete (which means it doesn’t work with infinite streams), then emits all of the events in the stream as a single array. This is useful for debugging because it eliminates the asynchronous nature of a stream, so you can see the entire stream as a single collection. In this example, without take(3), the stream would never end, and toArray would never emit a value.

 fromEvent(someButton, ​'click'​)
 .pipe(
  take(3),
  toArray()
 )
 .subscribe(console.log);

A third tool for debugging is the repeat operator, which does exactly what you think it does. When repeat is called on an observable, it waits for that observable to complete, then emits the values from the original observable however many times you specified. This example logs 1, 2, 3, 1, 2, 3, 1, 2, 3 (with each group of logs separated by one second).

 of​(1, 2, 3)
 .pipe(
  delay(1000),
  repeat(3)
 )
 .subscribe(console.log);

When debugging short, finite streams, repeat is helpful to continue the stream so that you can dig into what’s going on in your code (instead of refreshing the page every single time). It’s important to note here that repeat(n) emits the source values n times, so repeat(1) behaves just like an unmodified observable. Calling repeat without an argument results in an infinite observable, repeating until unsubscribed.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.114.28