Opérateurs utilitaires observables dans RxJava

Observable Utility Operators dans RxJava

1. Vue d'ensemble

Dans cet article, nous allons découvrir quelques opérateurs utilitaires pour travailler avecObservables dans RxJava et comment implémenter des opérateurs personnalisés.

An operator is a function that takes and alters the behavior of an upstream Observable<T> and returns a downstream Observable<R> or Subscriber, où les types T et R peuvent ou non être identiques.

Les opérateurs encapsulent lesObservables existants et les améliorent généralement en interceptant l'abonnement. Cela peut paraître compliqué, mais il est en fait assez flexible et facile à comprendre.

2. Do

Plusieurs actions peuvent modifier les événements du cycle de vie deObservable.

L'opérateur doOnNext modifie la sourceObservable 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, appel de la méthodeonCompleted deObserveronCompleted:

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

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

L'opérateurdoOnEach modifie la sourceObservable afin qu'il notifie unObserver pour chaque élément et établit un rappel qui sera appelé à chaque fois qu'un élément est émis.

L'opérateurdoOnSubscribe enregistre une action qui est appelée chaque fois qu'unObserver souscrit auObservable résultant.

Il y a aussi ledoOnUnsubscribe operator qui fait le contraire dedoOnSubscribe:

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"));

Lorsqu'unObservable se termine par une erreur, nous pouvons utiliser l'opérateurdoOnError pour effectuer une action.

DoOnTerminate operator enregistre une action qui sera appelée lorsqu'un Observable se termine, avec succès ou avec une erreur:

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"));

Il y a aussi unFinallyDo operator – which was deprecated in favor of doAfterTerminate. It registers an action when an Observable completes.

3. ObserveOn contreSubscribeOn

Par défaut, unObservable avec la chaîne d'opérateurs fonctionnera sur le même thread sur lequel sa méthodeSubscribe est appelée.

L'opérateur[.operator]#ObserveOn #pécifie unScheduler différent que lesObservable utiliseront pour envoyer des notifications à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);

Nous voyons que les éléments ont été produits dans lesmain thread et ont été poussés jusqu'au premier appel demap.

Mais après cela, lesobserveOn ont redirigé le traitement vers uncomputation thread, qui a été utilisé lors du traitement desmap et desSubscriber. finaux

One problem that may arise with observeOn is the bottom stream can produce emissions faster than the top stream can process them. Cela peut causer des problèmes avecbackpressure que nous devrons peut-être prendre en compte.

Pour spécifier sur quelsScheduler lesObservable doivent opérer, nous pouvons utiliser l'opérateursubscribeOn:

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 indique à la sourceObservable quel thread utiliser pour émettre des éléments - seul ce thread poussera les éléments vers lesSubscriber. Il peut être placé n'importe où dans le flux, car il affecte uniquement l'abonnement.

En effet, nous ne pouvons utiliser qu'un seulsubscribeOn, mais nous pouvons avoir n'importe quel nombre d'opérateursobserveOn. Nous pouvons facilement changer les émissions d'un thread à un autre en utilisantobserveOn.

4. Single etSingleOrDefault

L'opérateurSingle renvoie unObservable qui émet l'élément unique émis par la sourceObservable:

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

Si la sourceObservable produit zéro ou plus d'un élément, une exception sera levée:

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

D'autre part, l'opérateurSingleOrDefault est très similaire àSingle, ce qui signifie qu'il retourne également unObservable qui émet l'élément unique de la source, mais en plus, nous pouvons spécifier une valeur par défaut :

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

Mais si la sourceObservable émet plus d'un élément, elle émet toujours unIllegalArgumentExeption:

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

S conclusion simple:

  • S'il est prévu que la sourceObservable puisse avoir aucun ou un élément, alorsSingleOrDefault doit être utilisé

  • Si nous traitons potentiellement plus d'un élément émis dans notre`Observable` et que nous ne voulons émettre que la première ou la dernière valeur, nous pouvons utiliser d'autres opérateurs commefirst oulast

5. Timestamp

The Timestamp operator attaches a timestamp to each item emitted by the source Observable avant de réémettre cet élément dans sa propre séquence. L'horodatage indique à quelle heure l'élément a été émis:

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

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

6. Delay

Cet opérateur modifie sa sourceObservable en faisant une pause pendant un incrément de temps particulier avant d'émettre chacun des éléments sourceObservable’s.

Il compense toute la séquence en utilisant la valeur fournie:

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);

Il existe un opérateur alternatif, avec lequel nous pouvons retarder l'abonnement à la source Observable appelédelaySubscription.

L'opérateurDelay s'exécute sur lescomputationScheduler par défaut, mais nous pouvons choisir unScheduler différent en le passant comme troisième paramètre facultatif àdelaySubscription.

7. Repeat

Repeat intercepte simplement la notification d'achèvement en amont et plutôt que de la transmettre en aval, il se réabonne.

Par conséquent, il n'est pas garanti querepeat continuera à parcourir la même séquence d'événements, mais cela se trouve être le cas lorsque l'amont est un flux fixe:

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

assertTrue(receivedTotal == 18);

8. Cache

L'opérateurcache se situe entre lessubscribe et nosObservable personnalisés.

Lorsque le premier abonné apparaît,cache délègue l'abonnement auxObservable sous-jacents et transfère toutes les notifications (événements, achèvements ou erreurs) en aval.

Cependant, dans le même temps, il conserve une copie de toutes les notifications en interne. Lorsqu'un abonné suivant souhaite recevoir des notifications poussées,cache ne délègue plus auxObservable sous-jacents mais alimente à la place les valeurs mises en cache:

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

Lorsqu'unobserver souscrit auxObservable renvoyés par lesusing(), il utilisera la fonction d'usineObservable pour créer lesObservable lesobserver va… observer, tout en utilisant la fonction de fabrique de ressources pour créer la ressource pour laquelle nous l'avons conçue.

Lorsque leobserver se désabonne desObservable, ou lorsque leObservable se termine,using appellera la troisième fonction pour disposer de la ressource créée:

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. Conclusion

Dans cet article, nous avons expliqué comment utiliser les opérateurs de services publics RxJava et également explorer leurs fonctionnalités les plus importantes.

Le véritable pouvoir de RxJava réside dans ses opérateurs. Les transformations déclaratives de flux de données sont à la fois sûres, expressives et flexibles.

Avec une base solide en programmation fonctionnelle, les opérateurs jouent un rôle déterminant dans l'adoption de RxJava. La maîtrise des opérateurs intégrés est la clé du succès dans cette bibliothèque.

Le code source complet du projet, y compris tous les exemples de code utilisés ici, peut être trouvéover on GitHub.