web-dev-qa-db-ja.com

最初のアイテムをすぐに配信し、次のアイテムを「デバウンス」します

次のユースケースを検討してください。

  • できるだけ早く最初のアイテムを届ける必要がある
  • デバウンス 1秒のタイムアウトでイベントを追跡する必要がある

OperatorDebounceWithTimeに基づいてカスタム演算子を実装し、次のように使用しました

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

CustomOperatorDebounceWithTimeは最初のアイテムをすぐに配信し、OperatorDebounceWithTime演算子のロジックを使用してデバウンス後のアイテムに移動します。

説明されている動作を実現する簡単な方法はありますか? compose演算子をスキップしてみましょう。問題は解決しません。カスタムオペレーターを実装せずにこれを達成する方法を探しています。

33
tomrozb

更新:
@ loparのコメントからのより良い方法は次のとおりです。

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

このようなものは何ですか:

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

@LortRaydenMKと@loparの答えは最高ですが、あなたや似たような状況の人にとってより良い結果が得られる場合に備えて、何か他のものを提案したかったのです。

debounce()には、この特定のアイテムをデバウンスする時間を決定する関数を使用するバリアントがあります。これは、一定時間後に完了するオブザーバブルを返すことで指定します。関数は、最初のアイテムに対してempty()を返し、残りのアイテムに対してtimer()を返します。 (未テスト)のようなもの:

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

秘Theは、この関数が最初のアイテムを知る必要があるということです。あなたのシーケンスはそれを知っているかもしれません。そうでない場合は、Zip()または何かと一緒にrange()する必要があるかもしれません。その場合は、他の回答でソリューションを使用する方が適切です。

17

RxJSの同じ質問に対する答え から翻訳されたRxJava 2.0を使用した簡単なソリューション。これは、throttleFirstとdebounceを組み合わせてから、重複を削除します。

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

Limit()またはtake()を使用するアプローチは、継続的に観察したいかもしれないが、長期間存続するデータフローを処理するようには見えませんが、しばらくの間最初に見られるイベントに対してはすぐに動作します。

7
Adrian Baker

関数を受け取るdebounceのバージョンを使用し、この方法で関数を実装します。

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

最初のアイテムはすぐに放出されます。後続のアイテムは1秒遅れます。遅延オブザーバブルが次のアイテムが到着する前に終了しない場合、キャンセルされるため、予想されるデバウンス動作が満たされます。

6
Rich Ehmer

LordRaydenMKとloparの答え には問題があります。常に2番目のアイテムを失います。デバウンスがある場合、通常は多くのイベントがあり、2番目のイベントはデバウンスでディスカウントされるため、これまで誰もこれを実現していなかったと思います。イベントを失うことのない正しい方法は次のとおりです。

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

心配しないでください、重複したイベントを取得することはありません。わからない場合は、このコードを実行して自分で確認できます。

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

@loparのコメントに基づくKotlin拡張機能:

fun <T> Flowable<T>.debounceImmediate(timeout: Long, unit: TimeUnit): Flowable<T> {
    return publish {
        it.take(1).concatWith(it.debounce(timeout, unit))
    }
}

fun <T> Observable<T>.debounceImmediate(timeout: Long, unit: TimeUnit): Observable<T> {
    return publish {
        it.take(1).concatWith(it.debounce(timeout, unit))
    }
}
0
gswierczynski

Ngrx-rxjsソリューション、パイプを2つに分割

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