📝
フロントからの検索リクエストをRxJSでいい感じに制御したい
バージョン
RxJS: 7.8.1
仕様
- 500ms待ってからリクエストを送る
- リクエスト中に後続リクエストが来た場合は後勝ち(前のリクエストをキャンセルする)
- 検索ワードが2文字未満の場合はリクエストを送らずに結果を空にする
- サーバーがエラーからエラーが返ってきた場合は結果を空にする
結論
こんな感じが良さそうです。
this.fetchSubject$.pipe(
distinctUntilChanged(),
tap(() => {
this.items = []
this.isLoading = true;
}),
switchMap(keyword => iif(
() => keyword.length >= 2,
from([keyword]).pipe(
delay(500),
mergeMap(keyword => this.fetch(keyword)),
),
of([]),
)),
).subscribe(items => {
this.items = items
this.isLoading = false;
})
ざっくり説明
- キーワードが変わるまでは処理しない
- 最初に前回の結果を空にして、ローディング状態にする
- 先行リクエストがある場合はキャンセルする(switchMap便利)
- キーワードが2文字未満の場合はリクエストを送らない
- 500ms待ってからリクエストを送る
ポイント
5.の処理について、debounceTime
やdebounce
では期待していた動きにはなりませんでした。
愛すべきプロトタイプたち
1. setTimeoutを使う
clearTimeout
と unsubscribe
を両方考えないといけないのが面倒なので🙅。
そもそもRxJSを全然活用できていない。
private search(keyword: string): void {
if (this.fetchTimeoutId) {
clearTimeout(this.fetchTimeoutId)
}
this.fetchSubscription?.unsubscribe();
this.issues = [];
this.isLoading = true;
this.fetchTimeoutId = setTimeout(() => {
this.fetchSubscription = this.fetch(keyword)
.subscribe(issues => {
this.issues = issues
this.isLoading = false;
})
}, 500)
}
2. debounceTimeを使う
必要の無い時でも500msの待ち時間が発生してしまうので🙅。
検索リクエストを送らない場合は遅延させないようにしたい。
this.fetchSubject$.pipe(
distinctUntilChanged(),
tap(() => {
this.items = []
this.isLoading = true;
}),
debounceTime(500),
switchMap(keyword => iif(
() => keyword.length >= 2,
this.fetch(keyword),
of([]),
)),
).subscribe(items => {
this.items = items
this.isLoading = false;
})
3. debounceを使う
だいぶ良い感じではあるけど、先行リクエストをキャンセルする前に500msの待ち時間が発生してしまうので🙅。
リクエストをキャンセルしてから500ms待つようにしたい。
keyword
の長さチェックが2回入っているのもできればやめたい。
this.fetchSubject$.pipe(
distinctUntilChanged(),
tap(() => {
this.items = []
this.isLoading = true;
}),
debounce(keyword => timer(keyword.length >= 2 ? 500 : 0)),
switchMap(keyword => iif(
() => keyword.length >= 2,
this.fetch(keyword),
of([]),
)),
).subscribe(items => {
this.items = items
this.isLoading = false;
})
終わりに
ここが違うよ、こうすればもっと簡単にできるよ、などありましたら、コメントをいただけるととてもありがたいです。
Discussion