まず、これは私が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に依存します(これは循環オブザーバブルと呼ばれますか?)
-これは正しいアプローチですか?
-「オブザーバブルの循環」は良いことですが、可能ですか?(作成に問題がありました)。
-私が最初に試すべき他の方法は?
-相互依存の監視可能なストリームを作成することは可能ですか?
ご協力ありがとうございました。
この問題は複雑すぎます。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>
私は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>
以下は、HttpClientモジュールを使用してAngularクラスコンテキストでrxjs演算子expand
、reduce
および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), [])
);
}