RxJSのMergeMap、ConcatMap、switchMapの違い

2022/09/15に公開

皆さんこんにちは!!

現在株式会社エムアイ・ラボでは、Angularのリアクティブ・プログラミング用のライブラリであるRxJSを使用してます。このRxJSオペレータであるmergeMapをたまに使っていますが、mergeMapは、他に似たオペレータ(concatMapやswitchMap)があり、その違いについて色々と調べてみました。

今回は調べたmergeMap、concatMap、switchMapの違いについて記事にしていきたいと思います。
最初は理解しやすいmapとmergeMapから解説していきます。



■ mapオペレータ

map<T, R>(project: (value: T, index: number) => R, thisArg?: any): OperatorFunction<T, R>

mapオペレータはArray.prototype.map()と同じような役割を担います。
渡された値を呼び出し、それぞれの値に対して処理を実施し、新しい配列を生成します。戻り値はObservableです。

const values: Observable<number> = from([1, 2, 3]).pipe(
  map((v) => {
    return v * 2;
  })
);

values.subscribe({
  next(observer) {
    console.log(observer);
  },
  error(error) {
    console.log(`Error:`, error);
  },
  complete() {
    console.log('complete!!');
  },
});

// 結果
// 2
// 4
// 6
// complete!!



■ mergeMap

mergeMap(project: function: Observable, resultSelector: function: any, concurrent: number): Observable

mergeMapの特徴は以下の通りです。

  • 受け取った値をもとに新たなObservableを生成する。
  • 生成された複数のObservableを合成して、1つのObservableを生成する。



例えば、単純にfromオペレータ(Iterable な引数をとり、順番に流してObservableを生成)を使用した場合、出力された値は1,2,3のみです。

from([1, 2, 3]).subscribe({
  next(observer) {
    console.log(observer);
  },
  error(error) {
    console.log(`Error:`, error);
  },
  complete() {
    console.log('complete!!');
  },
});

// 処理結果
// 1
// 2
// 3
// complete!!



しかし、mergeMapオペレータを使用した場合は挙動が異なります。渡されたObservableな値それぞれに対して、処理が加えられていることが分かります。

const values: Observable<string> = from([1, 2, 3]).pipe(
  mergeMap((value) => {
    return of(`${value}A`, `${value}B`, `${value}C`);
  })
);

values.subscribe({
  next(observer) {
    console.log(observer);
  },
  error(error) {
    console.log(`Error:`, error);
  },
  complete() {
    console.log('complete!!');
  },
});

// 処理結果
// 1A
// 1B
// 1C
// 2A
// 2B
// 2C
// 3A
// 3B
// 3C
// complete!!



■ mergeMap、concatMap、switchMapの違い

mergeMapと似たオペレータとしてconcatMap、switchMapがあります。似たような名前でややこしいですが、それぞれ挙動が異なります。mergeMapの場合は、渡された値の順番に関わらず、非同期処理が解決した順番に処理が並べられます。この場合thirdが一番早く終了するので、最初に並ぶことになります。

const subject = new Subject();

subject
  .pipe(
    mergeMap((values) => {
      return setDelay(values.value, values.delay);
    })
  )
  .subscribe({
    next: (value) => {
      console.log(value);
    },
    error: (error) => {
      console.log(error);
    },
    complete: () => {
      console.log('complete!!');
    },
  });

subject.next({ value: 'first', delay: 500 });
subject.next({ value: 'second', delay: 700 });
subject.next({ value: 'third', delay: 100 });

function setDelay(value, delay) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(value);
    }, delay);
  });
}

// 処理結果
// third
// first
// second



■ concatMap

concatMap(project: function, resultSelector: function): Observable

concatMapの場合は、渡された値の順番で処理されます。
例えば先ほどのサンプルコードのmergeMapの箇所をconcatMapに変更してみます。delayの値が大きいにも関わらず、渡された順番で処理されていることがわかります。

const subject = new Subject();

subject
  .pipe(
    concatMap((values) => {
      return setDelay(values.value, values.delay);
    })
  )
  .subscribe({
    next: (value) => {
      console.log(value);
    },
    error: (error) => {
      console.log(error);
    },
    complete: () => {
      console.log('complete!!');
    },
  });

subject.next({ value: 'first', delay: 500 });
subject.next({ value: 'second', delay: 700 });
subject.next({ value: 'third', delay: 100 });

function setDelay(value, delay) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(value);
    }, delay);
  });
}

// 処理結果
// first
// second
// third



■ switchMap

switchMap(project: function: Observable, resultSelector: function(outerValue, innerValue, outerIndex, innerIndex): any): Observable

switchMapの場合は前の非同期処理が解決する前に、次の非同期処理が流れていくると、未解決の非同期処理はキャンセルされる特徴を持ちます。同じ容量でmergeMapの箇所をswitchMapに変更した場合、処理結果は一番早くに終了するthirdのみとなります。

// 処理結果
// third



■ まとめ

これまでの結果をまとめていきます。
mergeMap、concatMap、switchMapはそれぞれ

  • 受け取った値をもとに新たなObservableを生成する。
  • 生成された複数のObservableを合成して、1つのObservableを生成する。

...という特徴があります。

さらに、mergeMapは場合は、渡された値の順番に関わらず非同期処理が解決した順番に処理が並べられると特徴があり、concatMapは渡された値の順番通りに処理が実行、switchMapは最初に解決する非同期処理のみ処理が流れていくるということになります。


各オペレータの挙動をしっかり理解して、バグのないコードを心掛けたいですね!!
最後まで読んでいただきありがとうございました!!

Discussion