`first()` と `filter()` の使い方注意

first()filter() の使い方注意

特定のイベントを受けたときに BehaviorSubject を覗いて、条件が成り立っていたらなにか処理を行う、という実装をするときにこのように実装することがある。

function onSomeEvent() {
    someSubject$.pipe(
        first(),
        filter((someValue) => someValue.isHoge()),
    ).subscribe((someValue) => {
        // Process someValue
    });

これは問題ないのだが、よく first()filter() の順序を間違える事がある。

function onSomeEvent() {
    someSubject$.pipe(
        filter((someValue) => someValue.isHoge()),
        first(),
    ).subscribe((someValue) => {
        // Process someValue
    })
}

この場合、動くは動くが、someValue.isHoge() が偽の場合でも someSubject$ が開いたままになっているので、メモリリークの原因になってしまう。 必ず最初に first() を入れて、確実に complete 状態になるようにしよう。

また、うっかり first() を入れ忘れるのもよくある。

function onSomeEvent() {
    someSubject$.pipe(
        filter((someValue) => someValue.isHoge()),
    ).subscribe((someValue) => {
        // Process someValue
    })
}

この場合も、二重に動くので動作確認で異常なしに見えるし、単体テストもそのテスト自体は通ってしまうことがあるが、.unsubscribe() されていないので その次のテストで落ちることになる。

いずれの場合も原因が非常に分かりづらいので、注意されたし。

安全なプラクティスとしては、filter() を使わずに first() に条件式を渡してしまうことである。

function onSomeEvent() {
    someSubject$.pipe(
        first((someValue) => someValue.isHoge()),
    ).subscribe((someValue) => {
        // Process someValue
    })
}

これであれば、 filter() との順序や入れ忘れで悩むことは少なくなる。

別解としては、 .subscribe() の中でif文で判定してしまう方法もある。 シンプルなケースによってはこれでも良い。

function onSomeEvent() {
    someSubject$.pipe(
        first(),
    ).subscribe((someValue) => {
        if (!someValue.isHoge()) {
            return;
        }
        // Process someValue
    })
}

first() の場合と同じく、 take() も気をつける必要がある。

こちらは条件式を取らないので、順序には気をつけよう。


参考