Наблюдаемые операторы утилит в RxJava

Наблюдаемые служебные операторы в RxJava

1. обзор

В этой статье мы узнаем о некоторых служебных операторах для работы сObservables в RxJava и о том, как реализовать собственные.

An operator is a function that takes and alters the behavior of an upstream Observable<T> and returns a downstream Observable<R> or Subscriber, где типы T и R могут совпадать, а могут и не совпадать.

Операторы обертывают существующиеObservables и улучшают их, как правило, путем перехвата подписки. Это может показаться сложным, но на самом деле это довольно гибко и не так сложно понять.

2. Doс

Есть несколько действий, которые могут изменить события жизненного циклаObservable.

Оператор doOnNext изменяет источникObservable so that it invokes an action when the onNext is called .__

The doOnCompleted operator registers an action which is called if the resulting Observable terminates normally, вызывая методObserver‘sonCompleted:

Observable.range(1, 10)
  .doOnNext(r -> receivedTotal += r)
  .doOnCompleted(() -> result = "Completed")
  .subscribe();

assertTrue(receivedTotal == 55);
assertTrue(result.equals("Completed"));

ОператорdoOnEach изменяет источникObservable таким образом, чтобы он уведомлялObserver для каждого элемента и устанавливает обратный вызов, который будет вызываться каждый раз при отправке элемента.

ОператорdoOnSubscribe регистрирует действие, которое вызывается всякий раз, когдаObserver подписывается на результирующийObservable.

Также естьdoOnUnsubscribe operator, который действует противоположноdoOnSubscribe:

Observable.range(1, 10)
  .doOnEach(new Observer() {
      @Override
      public void onCompleted() {
          System.out.println("Complete");
      }
      @Override
      public void onError(Throwable e) {
          e.printStackTrace();
      }
      @Override
      public void onNext(Integer value) {
          receivedTotal += value;
      }
  })
  .doOnSubscribe(() -> result = "Subscribed")
  .subscribe();
assertTrue(receivedTotal == 55);
assertTrue(result.equals("Subscribed"));

КогдаObservable завершается с ошибкой, мы можем использовать операторdoOnError для выполнения действия.

DoOnTerminate operator регистрирует действие, которое будет вызываться, когда Observable завершится успешно или с ошибкой:

thrown.expect(OnErrorNotImplementedException.class);
Observable.empty()
  .single()
  .doOnError(throwable -> { throw new RuntimeException("error");})
  .doOnTerminate(() -> result += "doOnTerminate")
  .doAfterTerminate(() -> result += "_doAfterTerminate")
  .subscribe();
assertTrue(result.equals("doOnTerminate_doAfterTerminate"));

Также естьFinallyDo operator – which was deprecated in favor of doAfterTerminate. It registers an action when an Observable completes.

3. ObserveOn противSubscribeOn

По умолчаниюObservable вместе с цепочкой операторов будет работать в том же потоке, в котором вызывается его методSubscribe.

Оператор[.operator]#ObserveOn #указывает другойScheduler, которыйObservable будет использовать для отправки уведомленийObservers:

Observable.range(1, 5)
  .map(i -> i * 100)
  .doOnNext(i -> {
      emittedTotal += i;
      System.out.println("Emitting " + i
        + " on thread " + Thread.currentThread().getName());
  })
  .observeOn(Schedulers.computation())
  .map(i -> i * 10)
  .subscribe(i -> {
      receivedTotal += i;
      System.out.println("Received " + i + " on thread "
        + Thread.currentThread().getName());
  });

Thread.sleep(2000);
assertTrue(emittedTotal == 1500);
assertTrue(receivedTotal == 15000);

Мы видим, что элементы были созданы вmain thread и были продвинуты до первого вызоваmap.

Но после этогоobserveOn перенаправил обработку наcomputation thread, который использовался при обработкеmap и окончательногоSubscriber.

One problem that may arise with observeOn is the bottom stream can produce emissions faster than the top stream can process them. Это может вызвать проблемы сbackpressure, которые нам, возможно, придется учитывать.

Чтобы указать, на какомScheduler должен работатьObservable, мы можем использовать операторsubscribeOn:

Observable.range(1, 5)
  .map(i -> i * 100)
  .doOnNext(i -> {
      emittedTotal += i;
      System.out.println("Emitting " + i
        + " on thread " + Thread.currentThread().getName());
  })
  .subscribeOn(Schedulers.computation())
  .map(i -> i * 10)
  .subscribe(i -> {
      receivedTotal += i;
      System.out.println("Received " + i + " on thread "
        + Thread.currentThread().getName());
  });

Thread.sleep(2000);
assertTrue(emittedTotal == 1500);
assertTrue(receivedTotal == 15000);

SubscribeOn указывает источникуObservable, какой поток использовать для отправки элементов - только этот поток будет отправлять элементы вSubscriber. Его можно разместить в любом месте в потоке, поскольку он влияет только на подписку.

Фактически, мы можем использовать только одинsubscribeOn, но у нас может быть любое количество операторовobserveOn. Мы можем легко переключать выбросы из одного потока в другой, используяobserveOn.

4. Single иSingleOrDefault

ОператорSingle возвращаетObservable, который испускает единственный элемент, выпущенный источникомObservable:

Observable.range(1, 1)
  .single()
  .subscribe(i -> receivedTotal += i);
assertTrue(receivedTotal == 1);

Если источникObservable производит ноль или более одного элемента, будет выброшено исключение:

Observable.empty()
  .single()
  .onErrorReturn(e -> receivedTotal += 10)
  .subscribe();
assertTrue(receivedTotal == 10);

С другой стороны, операторSingleOrDefault очень похож наSingle,, что означает, что он также возвращаетObservable, который испускает единственный элемент из источника, но, кроме того, мы можем указать значение по умолчанию :

Observable.empty()
  .singleOrDefault("Default")
  .subscribe(i -> result +=i);
assertTrue(result.equals("Default"));

Но если источникObservable испускает более одного элемента, он все равно выдаетIllegalArgumentExeption:

Observable.range(1, 3)
  .singleOrDefault(5)
  .onErrorReturn(e -> receivedTotal += 10)
  .subscribe();
assertTrue(receivedTotal == 10);

S простой вывод:

  • Если ожидается, что источникObservable может не иметь ни одного элемента или иметь один элемент, тогда следует использоватьSingleOrDefault

  • Если мы имеем дело с потенциально более чем одним элементом, испускаемым в нашем`Observable`, и мы хотим испускать только первое или последнее значение, мы можем использовать другие операторы, такие какfirst илиlast

5. Timestampс

The Timestamp operator attaches a timestamp to each item emitted by the source Observable перед повторной отправкой этого элемента в его собственной последовательности. Отметка времени указывает, в какое время был выпущен элемент:

Observable.range(1, 10)
  .timestamp()
  .map(o -> result = o.getClass().toString() )
  .last()
  .subscribe();

assertTrue(result.equals("class rx.schedulers.Timestamped"));

6. Delayс

Этот оператор изменяет свой источникObservable, делая паузу на определенное время перед выдачей каждого из элементов источникаObservable’s.

Смещает всю последовательность, используя предоставленное значение:

Observable source = Observable.interval(1, TimeUnit.SECONDS)
  .take(5)
  .timestamp();

Observable delayedObservable
  = source.delay(2, TimeUnit.SECONDS);

source.subscribe(
  value -> System.out.println("source :" + value),
  t -> System.out.println("source error"),
  () -> System.out.println("source completed"));

delayedObservable.subscribe(
  value -> System.out.println("delay : " + value),
  t -> System.out.println("delay error"),
  () -> System.out.println("delay completed"));
Thread.sleep(8000);

Есть альтернативный оператор, с помощью которого мы можем отложить подписку на исходный Observable под названиемdelaySubscription.

ОператорDelay по умолчанию работает наcomputationScheduler, но мы можем выбрать другойScheduler, передав его в качестве необязательного третьего параметра вdelaySubscription.

7. Repeatс

Repeat просто перехватывает уведомление о завершении от восходящего потока и вместо того, чтобы передавать его ниже по течению, он повторно подписывается.

Следовательно, не гарантируется, чтоrepeat будет продолжать циклически проходить ту же последовательность событий, но это случается, когда восходящий поток является фиксированным потоком:

Observable.range(1, 3)
  .repeat(3)
  .subscribe(i -> receivedTotal += i);

assertTrue(receivedTotal == 18);

8. Cacheс

Операторcache стоит междуsubscribe и нашим пользовательскимObservable.

Когда появляется первый подписчик,cache делегирует подписку базовомуObservable и пересылает все уведомления (события, завершения или ошибки) ниже по потоку.

Однако в то же время он хранит копии всех уведомлений внутри страны. Когда последующий подписчик хочет получать push-уведомления,cache больше не делегирует базовыйObservable, а вместо этого передает кэшированные значения:

Observable source =
  Observable.create(subscriber -> {
      System.out.println("Create");
      subscriber.onNext(receivedTotal += 5);
      subscriber.onCompleted();
  }).cache();
source.subscribe(i -> {
  System.out.println("element 1");
  receivedTotal += 1;
});
source.subscribe(i -> {
  System.out.println("element 2");
  receivedTotal += 2;
});

assertTrue(receivedTotal == 8);

9. Usingс

Когдаobserver подписывается наObservable, возвращаемый изusing(), он будет использовать фабричную функциюObservable для созданияObservableobserver будет… наблюдать, в то же время используя функцию фабрики ресурсов для создания любого ресурса, который мы разработали для этого.

Когдаobserver отписывается отObservable или когдаObservable завершается,using вызывает третью функцию для удаления созданного ресурса:

Observable values = Observable.using(
  () -> "resource",
  r -> {
      return Observable.create(o -> {
          for (Character c : r.toCharArray()) {
              o.onNext(c);
          }
          o.onCompleted();
      });
  },
  r -> System.out.println("Disposed: " + r)
);
values.subscribe(
  v -> result += v,
  e -> result += e
);
assertTrue(result.equals("resource"));

10. Заключение

В этой статье мы поговорили о том, как использовать служебные операторы RxJava, а также о том, как исследовать их наиболее важные функции.

Истинная сила RxJava заключается в его операторах. Декларативные преобразования потоков данных безопасны, но выразительны и гибки.

Имея прочную основу для функционального программирования, операторы играют решающую роль в принятии RxJava. Освоение встроенных операторов является ключом к успеху в этой библиотеке.

Полный исходный код проекта, включая все используемые здесь примеры кода, можно найти вover on GitHub.