web-dev-qa-db-fra.com

Livrer le premier article immédiatement, 'rebounce' éléments suivants

Considérez le cas d'utilisation suivant:

  • besoin de livrer le premier article le plus tôt possible
  • besoin de debounce des événements suivants avec un délai d'expiration d'une seconde

J'ai fini par implémenter l'opérateur personnalisé basé sur OperatorDebounceWithTime puis l'utiliser comme ceci

.lift(new CustomOperatorDebounceWithTime<>(1, TimeUnit.SECONDS, Schedulers.computation()))

CustomOperatorDebounceWithTime livre le premier élément immédiatement puis utilise la logique de l'opérateur OperatorDebounceWithTime pour debounce des éléments ultérieurs.

Existe-t-il un moyen plus facile d'obtenir le comportement décrit? Sautons l'opérateur compose, cela ne résout pas le problème. Je cherche un moyen d'y parvenir sans implémenter d'opérateurs personnalisés.

25
tomrozb

Mettre à jour:
D'après les commentaires de @ lopar, une meilleure solution serait:

Observable.from(items).publish(publishedItems -> publishedItems.limit(1).concatWith(publishedItems.skip(1).debounce(1, TimeUnit.SECONDS)))

Est-ce que quelque chose comme ce travail:

String[] items = {"one", "two", "three", "four", "five", "six", "seven", "eight"};
Observable<String> myObservable = Observable.from(items);
Observable.concat(myObservable.first(), myObservable.skip(1).debounce(1, TimeUnit.SECONDS))
    .subscribe(s -> System.out.println(s));
30
LordRaydenMK

Les réponses de @LortRaydenMK et @lopar sont les meilleures, mais je voulais suggérer autre chose au cas où cela fonctionnerait mieux pour vous ou pour quelqu'un dans une situation similaire.

Il existe une variante de debounce() qui prend une fonction qui détermine le temps d’attente de cet élément. Il spécifie cela en renvoyant une observable qui se termine après un certain temps. Votre fonction pourrait renvoyer empty() pour le premier élément et timer() pour le reste. Quelque chose comme (non testé):

String[] items = {"one", "two", "three", "four", "five", "six"};
Observable.from(items)
    .debounce(item -> item.equals("one")
            ? Observable.empty()
            : Observable.timer(1, TimeUnit.SECONDS));

L'astuce est que cette fonction devrait savoir quel élément est le premier. Votre séquence pourrait le savoir. Si ce n'est pas le cas, vous devrez peut-être Zip() avec range() ou quelque chose du genre. Mieux vaut dans ce cas utiliser la solution dans l'autre réponse.

16

Utilisez la version de debounce qui prend une fonction et implémentez-la de cette manière: 

    .debounce(new Func1<String, Observable<String>>() {
        private AtomicBoolean isFirstEmission = new AtomicBoolean(true);
        @Override
        public Observable<String> call(String s) {
             // note: standard debounce causes the first item to be
             // delayed by 1 second unnecessarily, this is a workaround
             if (isFirstEmission.getAndSet(false)) {
                 return Observable.just(s);
             } else {
                 return Observable.just(s).delay(1, TimeUnit.SECONDS);
             }
        }
    })

Le premier élément émet immédiatement. Les articles suivants sont retardés d'une seconde. Si une observable retardée ne se termine pas avant l'arrivée de l'élément suivant, elle est annulée et le comportement anti-rebond attendu est donc rempli.

6
Rich Ehmer

Une solution simple utilisant RxJava 2.0, traduite de la réponse à la même question pour RxJS , qui combine throttleFirst et debounce, puis supprime les doublons.

private <T> ObservableTransformer<T, T> debounceImmediate() {
    return observable  -> observable.publish(p -> 
        Observable.merge(p.throttleFirst(1, TimeUnit.SECONDS), 
            p.debounce(1, TimeUnit.SECONDS)).distinctUntilChanged());
} 

@Test
public void testDebounceImmediate() {
    Observable.just(0, 100, 200, 1500, 1600, 1800, 2000, 10000)
        .flatMap(v -> Observable.timer(v, TimeUnit.MILLISECONDS).map(w -> v))
        .doOnNext(v -> System.out.println(LocalDateTime.now() + " T=" + v))
            .compose(debounceImmediate())
            .blockingSubscribe(v -> System.out.println(LocalDateTime.now() + " Debounced: " + v));
}

L'approche consistant à utiliser limit () ou take () ne semble pas prendre en charge les flux de données de longue durée, que je souhaiterais peut-être observer en permanence, mais qui agit néanmoins immédiatement pour le premier événement observé pendant un certain temps.

5
Adrian Baker

La réponse de LordRaydenMK et lopar a un problème: vous perdez toujours le deuxième élément. Je suppose que personne ne l'a déjà fait auparavant, car si vous avez un effet anti-rebond, vous avez normalement beaucoup d'événements et le second est de toute façon annulé. La bonne façon de ne jamais perdre un événement est la suivante:

observable
    .publish(published ->
        published
            .limit(1)
            .concatWith(published.debounce(1, TimeUnit.SECONDS)));

Et ne vous inquiétez pas, vous n'allez pas avoir un événement dupliqué. Si vous n'êtes pas sûr, vous pouvez exécuter ce code et le vérifier vous-même:

Observable.just(1, 2, 3, 4)
    .publish(published ->
        published
            .limit(1)
            .concatWith(published))
    .subscribe(System.out::println);
2
Brais Gabin

Ngrx - solution rxjs, diviser le tuyau en deux 

onMyAction$ = this.actions$
    .pipe(ofType<any>(ActionTypes.MY_ACTION);

lastTime = new Date();

@Effect()
onMyActionWithAbort$ = this.onMyAction$
    .pipe(
        filter((data) => { 
          const result = new Date() - this.lastTime > 200; 
          this.lastTime = new Date(); 
          return result; 
        }),
        switchMap(this.DoTheJob.bind(this))
    );

@Effect()
onMyActionWithDebounce$ = this.onMyAction$
    .pipe(
        debounceTime(200),
        filter(this.preventDuplicateFilter.bind(this)),
        switchMap(this.DoTheJob.bind(this))
    );
0
Asaf