web-dev-qa-db-ja.com

RxJS takeWhileが最後の値を含む

私はこのようなRxJS5パイプラインを持っています

Rx.Observable.from([2, 3, 4, 5, 6])
  .takeWhile((v) => { v !== 4 })

4が表示されるまでサブスクリプションを保持したいが、最後の要素4も結果に含めたい。したがって、上記の例は

2, 3, 4

ただし、 公式ドキュメント によると、takeWhile演算子は包括的ではありません。つまり、指定した述語と一致しない要素に遭遇すると、最後の要素なしでストリームをすぐに完了します。その結果、上記のコードは実際に出力されます

2, 3

だから私の質問は、takeWhileを達成する最も簡単な方法は何ですか?また、RxJSで最後の要素を出力するのですか?

18
Fang-Pen Lin

RxJS 6.4.0以降、takeWhile(predicate, true)でこれが可能になりました。

オプションのinclusiveパラメーターをtakeWhileに追加する開かれたPRがすでにあります: https://github.com/ReactiveX/rxjs/pull/4115

少なくとも2つの可能な回避策があります。

  1. concatMap()を使用:

    _of('red', 'blue', 'green', 'orange').pipe(
      concatMap(color => {
        if (color === 'green') {
          return of(color, null);
        }
        return of(color);
      }),
      takeWhile(color => color),
    )
    _
  2. multicast()の使用:

    _of('red', 'blue', 'green', 'orange').pipe(
      multicast(
        () => new ReplaySubject(1),
        subject => subject.pipe(
          takeWhile((c) => c !== 'green'),
          concat(subject.take(1),
        )
      ),
    )
    _

私はこの演算子も使用してきたので、独自の追加のRxJS 5演算子のセットを作成しました。 https://github.com/martinsik/rxjs-extra#takewhileinclusive

この演算子は、このRxJS 5の問題でも説明されています: https://github.com/ReactiveX/rxjs/issues/242

2019年1月:RxJS 6用に更新

26
martin

UPDATE 2019年3月、rsjxバージョン6.4.0takeWhileには、オプションでinclusiveパラメータが追加され、条件を破る最初の要素を保持できるようになりました。したがって、解決策は単にtakeWhileの2番目の引数としてtrueを渡すことです:

import { takeWhile } from 'rxjs/operators';
import { from } from 'rxjs';

const cutOff = 4.5
const example = from([2, 3, 4, 5, 6])
.pipe(takeWhile(v => v < cutOff, true ))
const subscribe = example.subscribe(val =>
  console.log('inclusive:', val)
);

出力:

inclusive: 2
inclusive: 3
inclusive: 4
inclusive: 5

ここに住んで:

https://stackblitz.com/edit/TypeScript-m7zjkr?embed=1&file=index.ts

5が条件を破る最初の要素であることに注意してください。 v < cutOffのような動的条件があり、最後の要素が何であるかがわからない場合、endWithは実際には解決策ではないことに注意してください。

このプルリクエストの存在を指摘してくれた@martinに感謝します。

5
Batato

(多くのRxJSコードとは異なり)endWith(value)を使用できます。

const example = source.pipe(
                            takeWhile(val => val != 4), 
                            endWith(4));

PS。また、takeUntilは述語をとらないため、その演算子を使用してこの問題を解決しようとしてもできないことに注意してください。これはまったく異なるメソッドシグネチャです。

公式ドキュメント:https://rxjs-dev.firebaseapp.com/api/operators/endWith

https://stackblitz.com/edit/TypeScript-pvuawt

4
Simon_Weaver

!==のように、最後の要素が正確にわかるような比較の場合は、自分で再追加できます。

Rx.Observable.from([2, 3, 4, 5, 6])
  .takeWhile((v) => v !== 4)
  .concat(Rx.Observable.of(4))
  .subscribe(console.log)
3

私は同じ問題に遭遇しました、最後の要素を含める必要があったので、サブスクリプションへの参照を保持し、サブスクライブを解除することを選択しましたwithin theonNextcallback条件が満たされたとき。あなたのサンプルコードを使用すると、次のようになります。

const subscription = Observable.of('red', 'blue', 'green', 'orange')
  .subscribe(color => {
    // Do something with the value here
    if (color === 'green') {
      subscription.unsubscribe()
    }
  }) 

これは私にとってもうまくいきました。それは、観測可能な光源が放射を停止する原因にもなりました。これは、私のシナリオで必要なものです。私はtakeWhile演算子を使用していないことを認識していますが、主な目的は達成されており、回避策や追加のコードは必要ありません。私は、設計されていない方法で物事を強制するのが好きではありません。これの欠点は次のとおりです。

  • サブスクライブしている他のオブザーバーがいる場合、ソースは放出し続けます。
  • 最後のオブザーバーがサブスクライブを解除した場合、onCompletedは何らかの理由で呼び出されませんが、ソースが実際に放出を停止することを確認しました。
2

私の場合、最終的な値がどうなるかを予測できませんでした。また、一般的で簡単な演算子を使用したソリューションも必要でした。また、再利用できるものが必要だったので、真実であるという値に依存できませんでした。私が考え得る唯一のことは、次のように自分の演算子を定義することでした:

import { pipe, from } from 'rxjs';
import { switchMap, takeWhile, filter, map } from 'rxjs/operators';

export function doWhile<T>(shouldContinue: (a: T) => boolean) {
  return pipe(
    switchMap((data: T) => from([
      { data, continue: true },
      { data, continue: shouldContinue(data), exclude: true }
    ])),
    takeWhile(message => message.continue),
    filter(message => !message.exclude),
    map(message => message.data)
  );
}

少し変ですが、私にとってはうまくいき、インポートして使用することができます。

1
Michael Pearson