web-dev-qa-db-ja.com

RxJS MergeMapの仕組み

mergeMapの目的がまったくわかりません。 2つの「説明」を聞いたことがあります。

  1. LINQの「SelectAllに似ています」-いいえ。
  2. 「まあ、それはRxJS mergemapの組み合わせです-いいえ(または、これを複製できません)。

次のコードを検討してください。

    var obs1 = new Rx.Observable.interval(1000);
    var obs2 = new Rx.Observable.interval(1000);

    //Just a merge and a map, works fine
    obs1.merge(obs2).map(x=> x+'a').subscribe(
      next => console.log(next)
    )

    //Who know what - seems to do the same thing as a plain map on 1 observable
    obs1.mergeMap(val => Rx.Observable.of(val + `B`))
        .subscribe(
          next => console.log(next)
        )

JS Bin

「Who knows what」というラベルの付いた最後の部分は、obs1上のマップにすぎません-ポイントは何ですか?

mergeMapは実際に何をしますか?有効なユースケースの例は何ですか? (できればいくつかのコードで)

まったく役に立たなかった記事(上記のmergeMapコードは次のいずれかです): 12

44
VSO

tl; dr;mergeMapは、mapよりもはるかに強力です。 mergeMapを理解することは、Rxの全機能にアクセスするために必要な条件です。


類似点

  • mergeMapmapの両方が単一のストリームに作用します(対ZipcombineLatest

  • mergeMapmapの両方がストリームの要素を変換できます(vs. filterdelay

違い

地図

  • ソースストリームのサイズを変更することはできません(仮定:map自体はthrowではありません);ソースからの各要素に対して、厳密に1つのmapped要素が発行されます。 mapは要素を無視できません(たとえばfilterなど)。

  • デフォルトのスケジューラの場合、変換は同期的に行われます。 100%クリア:ソースストリームはその要素を非同期に配信できますが、次の各要素はすぐにmappedになり、さらに再送信されます。 mapは、たとえばdelayのように時間内に要素をシフトできません

  • 戻り値に制限はありません

  • idx => x

mergeMap

  • ソースストリームのサイズを変更できます。各要素には、任意の数(0、1、または多く)の新しい要素が作成/放出される場合があります

  • 非同期性を完全に制御できます-新しい要素が作成/放出されるときと、ソースストリームから同時に処理される要素の数の両方。たとえば、ソースストリームが10個の要素を放出したが、maxConcurrencyが2に設定されている場合、最初の2つの要素がすぐに処理され、残りの8つはバッファリングされます。処理されたcompletedの1つが処理されると、ソースストリームの次の要素などが処理されます-少し注意が必要ですが、以下の例を見てください

  • 他のすべての演算子は、単にmergeMapおよびObservableコンストラクターで実装できます。

  • 再帰的な非同期操作に使用できます

  • 戻り値はObservable型である必要があります(または、Rxはそれからobservableを作成する方法を知っている必要があります-例えば、promise、array

  • idx => Rx.Observable.of(x)

アレイアナロジー

let array = [1,2,3]
fn             map                    mergeMap
x => x*x       [1,4,9]                error /*expects array as return value*/
x => [x,x*x]   [[1,1],[2,4],[3,9]]    [1,1,2,4,3,9]

アナロジーは全体像を示しておらず、基本的に.mergeMapに対応し、maxConcurrencyが1に設定されています。このような場合、要素は上記のように順序付けられますが、通常はそうである必要はありません。私たちが持っている唯一の保証は、新しい要素の放出は、基礎となるストリーム内のそれらの位置による順序であることです。例:[3,1,2,4,9,1][2,3,1,1,9,4]は有効ですが、[1,1,4,2,3,9]は有効ではありません(4が基になるストリームで2の後に発行されたため)。

mergeMapを使用したいくつかの例:

// implement .map with .mergeMap
Rx.Observable.prototype.mapWithMergeMap = function(mapFn) {
  return this.mergeMap(x => Rx.Observable.of(mapFn(x)));
}

Rx.Observable.range(1, 3)
  .mapWithMergeMap(x => x * x)
  .subscribe(x => console.log('mapWithMergeMap', x))

// implement .filter with .mergeMap
Rx.Observable.prototype.filterWithMergeMap = function(filterFn) {
  return this.mergeMap(x =>
    filterFn(x) ?
    Rx.Observable.of(x) :
    Rx.Observable.empty()); // return no element
}

Rx.Observable.range(1, 3)
  .filterWithMergeMap(x => x === 3)
  .subscribe(x => console.log('filterWithMergeMap', x))

// implement .delay with .mergeMap 
Rx.Observable.prototype.delayWithMergeMap = function(delayMs) {
  return this.mergeMap(x =>
    Rx.Observable.create(obs => {
      // setTimeout is naive - one should use scheduler instead
      const token = setTimeout(() => {
        obs.next(x);
        obs.complete();
      }, delayMs)
      return () => clearTimeout(token);
    }))
}

Rx.Observable.range(1, 3)
  .delayWithMergeMap(500)
  .take(2)
  .subscribe(x => console.log('delayWithMergeMap', x))

// recursive count
const count = (from, to, interval) => {
  if (from > to) return Rx.Observable.empty();
  return Rx.Observable.timer(interval)
    .mergeMap(() =>
      count(from + 1, to, interval)
      .startWith(from))
}

count(1, 3, 1000).subscribe(x => console.log('count', x))

// just an example of bit different implementation with no returns
const countMoreRxWay = (from, to, interval) =>
  Rx.Observable.if(
    () => from > to,
    Rx.Observable.empty(),
    Rx.Observable.timer(interval)
    .mergeMap(() => countMoreRxWay(from + 1, to, interval)
      .startWith(from)))

const maxConcurrencyExample = () =>
  Rx.Observable.range(1,7)
    .do(x => console.log('emitted', x))
    .mergeMap(x => Rx.Observable.timer(1000).mapTo(x), 2)
    .do(x => console.log('processed', x))
    .subscribe()

setTimeout(maxConcurrencyExample, 3100)
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.1.1/Rx.min.js"></script>
100
artur grzesiak

.mergeMap()を使用すると、高次のObservableを単一のストリームにフラット化できます。例えば:

Rx.Observable.from([1,2,3,4])
  .map(i => getFreshApiData())
  .subscribe(val => console.log('regular map result: ' + val));

//vs

Rx.Observable.from([1,2,3,4])
  .mergeMap(i => getFreshApiData())
  .subscribe(val => console.log('mergeMap result: ' + val));

function getFreshApiData() {
  return Rx.Observable.of('retrieved new data')
    .delay(1000);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.1.0/Rx.js"></script>

.xxxMap()演算子の詳細な説明については、この他の質問の私の答えを参照してください: Rxjs-配列内の複数の値を抽出し、それらを観測可能なストリームにフィードバックするには同期的に

20