web-dev-qa-db-ja.com

RxJsの観察可能なページネーション

まず、これは私がRxJを使用する最初のプロジェクトです。私はそれを使用することで最もよく学ぶと思いました。

私はこの答えを見つけました: ページングされたリクエストをRxJで監視可能なストリームに変える しかし、それはコメントで述べています:

まだ最大コールスタックを超えています。約430ページに戻りました。再帰はここでの最善の解決策ではないかもしれません

Youtube Data APIにクエリを実行したいのですが、結果がページに戻ってきて、それらをページ分割する必要があります。私は次のようなワークフローが機能することを想像しました:1)呼び出しを開始する2)応答に「nextPageToken」があるかどうかを確認する3)ある場合は、YouTube APIに別のリクエストを実行する4)ない場合は、終了する

So to do this I could Imagine the following Observables / streams:
FirstRequestStream -A-X--------------->
ResponseStream     -A-A-A-A--X-------->
RequestStream      -I-A-I-A----------->
A = Action
I = Info from upper stream
X = Termination

(この図が私が作った方法が正しいかどうかはわかりません)

したがって、ResponseStreamはFirstRequestStreamとRequestStreamに依存します(マージ機能を使用)。 RequestStreamはResponseStreamに依存します(これは循環オブザーバブルと呼ばれますか?)

-これは正しいアプローチですか?

-「オブザーバブルの循環」は良いことですが、可能ですか?(作成に問題がありました)。

-私が最初に試すべき他の方法は?

-相互依存の監視可能なストリームを作成することは可能ですか?

ご協力ありがとうございました。

18
jjuser19jj

この問題は複雑すぎます。defer演算子を使用すると、はるかに簡単に解決できます。

考えられるのは、据え置きオブザーバブルを作成し(サブスクリプション後にのみ作成され、データのフェッチを開始する)、同じオブザーバブルと連結しますが、次のページにも連結します。次のページにも連結されます。 ……そして、それらすべては再帰なしで行うことができます。

コードは次のようになります。

const { defer, from, concat, EMPTY, timer } = rxjs; // = require("rxjs")
const { mergeMap, take, mapTo, tap } = rxjs.operators; // = require("rxjs/operators")

// simulate network request
function fetchPage(page=0) {
  return timer(100).pipe(
    tap(() => console.log(`-> fetched page ${page}`)),
    mapTo({
      items: Array.from({ length: 10 }).map((_, i) => page * 10 + i),
      nextPage: page + 1,
    })
  );
}

const getItems = page => defer(() => fetchPage(page)).pipe(
  mergeMap(({ items, nextPage }) => {
    const items$ = from(items);
    const next$ = nextPage ? getItems(nextPage) : EMPTY;
    return concat(items$, next$);
  })
);

// process only first 30 items, without fetching all of the data
getItems()
 .pipe(take(30))
 .subscribe(e => console.log(e));
<script src="https://unpkg.com/[email protected]/bundles/rxjs.umd.min.js"></script>
27
Oles Savluk

私はOles SavlukのコードスニペットをfetchPage関数を使って恥知らずに再利用し、 expand

ニコラス・ジェイミソンによる拡大に関する記事

再帰がexpandの呼び出しに隠されている、やや単純なコードが提供されます(必要に応じて、記事のコメントにコードを線形化する方法が示されています)。

const { timer, EMPTY } = rxjs; // = require("rxjs")
const { concatMap, expand, mapTo, tap, toArray } = rxjs.operators; // = require("rxjs/operators")

// simulate network request
const pageNumber = 3;
function fetchPage(page = 0) {
  return timer(1000).pipe(
    tap(() => console.log(`-> fetched page ${page}`)),
    mapTo({
      items: Array.from({ length: 10 }).map((_, i) => page * 10 + i),
      nextPage: ++page === pageNumber ? undefined : page,
    }),
  );
}

const list = fetchPage().pipe(
  expand(({ nextPage }) => nextPage ? fetchPage(nextPage) : EMPTY),
  concatMap(({ items }) => items),
  // Transforms the stream of numbers (Observable<number>)
  // to a stream with only an array of numbers (Observable<number[]>).
  // Remove if you want a stream of numbers, not waiting for all requests to complete.
  toArray(),
);

list.subscribe(console.log);
<script src="https://unpkg.com/[email protected]/bundles/rxjs.umd.min.js"></script>
5
PhiLho

以下は、HttpClientモジュールを使用してAngularクラスコンテキストでrxjs演算子expandreduceおよびemptyを使用する私のソリューションです。

API応答が、プロパティ 'data'(結果アイテムの配列)と 'next'(次のページのURLを含む文字列、または次のアイテムがない場合はnull)を含むオブジェクトであるとします。

getAllResults(url) {
  return this.http.get(url).pipe(
    expand((res) => res.next ? this.http.get(next) : EMPTY),
    reduce((acc, res) => acc.concat(res.data), [])
  );
}
3
LuJaks