简单理解 mergeMap, switchMap, concatMap, exhaustMap

3,913 阅读5分钟

因为感觉对这几个观察者映射理解不够充分,所以找到了一个神奇的网站。它可以帮助你充分分辨这些 map。

首先我们以这两个源为标准。

A:

012 B:

101010 现在,我们把A和B两个源做一个 乘法操作,也就是源A的每一个输出,都跟源B的每一个输出做乘法。

首先我们定义一下我们的源。

const { interval} = Rx;
const { take, map } = RxOperators;

const sourceA = (time) => interval(time).pipe(take(3));
const sourceB = (time, value) => interval(time).pipe(
  take(3),
  map(() => value * 10)
)

mergeMap

为了研究 mergeMap 是怎么运作的,我们可以通过以下代码来工作:

sourceA(1000).pipe(
  mergeMap((value) => sourceB(1500, value))
)

把上面的代码复制到神奇的网站上,你就可以观察到,它是这样工作的。之后的代码也是通过这种方式展示的。

010020100201020 我们可以看到,这样看可能不太直观,但是我们将他们分别打印,你就能看出区别了:

101010 012 要理解 mergeMap,第一点就是,它会直到两个源都输出数据的时候才会输出。那么,当源A输出后,需要等 1.5s 的时间等待源B的输出。而后,源A每隔1s就会有下一个输出,源B每隔1.5s后就会有下一个输出,所以,我们可以看到,为了保证后面的时序不被打乱,mergeMap 把合成之后的输出分别按照源A和源B的时序输出了。

以源A的角度看,它的每一个输出和源B的三个输出结合的输出的时序是源B的;以源B的角度看,它的每一个输出和源A的三个输出结合的输出的时序是源A的。 所以mergeMap的作用是保证输出后的原有叠加的时序不变。

switchMap

到了switchMap,我们可以看看输入到网站的代码。

sourceA(1000).pipe(
  switchMap((value) => sourceB(1500, value))
);

然后,看看它的输出是怎么样的。

202020

诶,怎么只有这么源A和源B的三个输出结合的输出?改一下时间,我们把源B的时间改成 800ms,再来看看。

010202020

好了,看到源A的前两个输出只和源B结合了一次,源A的最后一个输出却都结合了。我们还是看看他们分别的输出。

012

源 A 输出

101010

源 B 输出
我们可以对比看出,每当源A发出一个新的输出,之前源A跟源B结合的输出都被截断。那么,可以看出源B的每次输出与源A的输出结合的输出都依赖于源A的时序,当源A发出新输出的时候,之前结合的所有未完成输出都会被截断。

所以,这也解释了,为什么第一次源A只有最后一次的输出与源B结合。因为,源B每次输出的时序用时为1500ms,这个用时大于源A输出的用时。所以,当源A发出的第一次的输出准备与源B结合时(此时,源A需要等待源B运行完),源A的第二次的输出也发出了,由于switchMap的截断机制,便会跳过源A第一次的结合,以此类推。

concatMap

惯例,我们还是看看代码。

sourceA(500).pipe(
  concatMap((value) => sourceB(1000, value))
);

下面是 concatMap 的输出。

000101010202020

我们可以看到,concatMap 并不会截断,它跟mergeMap的输出内容一样,都是会把所有结合都输出。但是有一点,concatMap 输出结合的方式又跟mergeMap不太一样,它会等待上一次结合输出完毕后,才进行下一次的结合输出。也就是当 0,0,0 输出完毕后,才会输出 10,10,10。在这里, 时序会遵循以下规则,如果源B结合完了源A的数据,源A仍未发送下一条数据,源B将会等到源A的下一条数据的到来。如果源A的数据有堆积(或者A的时序比B的时序小),而源B仍未结合完,那么将会以源B的时序进行下一组结合。

exhaustMap

还是惯例,这是最后一个了,看看代码吧。

sourceA(1000).pipe(
  exhaustMap((value) => sourceB(500, value))
);

下面是 exhaustMap的输出。

000202020

可以看到,10 这个输出被截断了,它跟concatMap的区别就是,源A与源B的结合输出完毕后,并不会等待下一组结合的输出。如果在第一组结合输出完毕前,第二组结合就要开始输出的时候,exhaustMap会阻止第二组结合的输出,直到第一组结合完全输出完毕。

这样,我们不妨假设在它们开始输出的时候为第0秒,源A与源B的第一组结合完全输出,需要耗费 1.5秒的时间,也就是它会在第1.5秒处完全输出。而源A的第二组结合会在第1秒输出,由于exhaustMap的特性,第二组结合将不会发生,因为,1.5秒大于1秒。而第一组结合完全输出后,第三组结合会在第三秒输出,这样便会输出 20,20,20。

总结

本文主要是帮助你深入理解RxJs中的几个概念。下面是一些参考文档和工具。

Rxjs 官方文档

Rxjs 可视化工具