(三)温故知新系列之RXJS——RXJS操作符基础(合并类)

412 阅读10分钟

前言

在RxJS的世界中,为了满⾜复杂的需求,往往需要把不同来源 的数据汇聚在⼀起,把来⾃多个Observable对象的数据合并到⼀个 Observable对象中。

RxJS提供了众多操作符⽀持数据流合并,具体使⽤哪种操作符,要根 据待解决的问题决定,下⾯列举了各种场景下适⽤的合并类操作符:

  • 把多个数据流以收尾相连方式合并—— concat 和 concatAll

  • 把多个数据流以先到先得方式合并—— merge 和 mergeAll

  • 把多个数据流以一一对应的方式合并—— zip 和 zipAll

  • 持续合并多个数据流中最新产生的数据——combineLatest、combineAll 和 withLatesfrom

  • 从多个数据流中选取第一个产生内容的数据流——race

  • 在数据流前面添加一个指定数据——startWith

  • 只获取多个数据流最后产生的那个数据———forkJoin

  • 从高阶数据流中切换数据源——switch 和 exhaust

RxJS提供了⼀系列可以完成 Observable 组合操作的操作符,这⼀类操 作符称为合并类操作符,这类操作符都有多个 Observable 对象作为数据来源,把不同来源的数据根据不同的规则合并到⼀个 Observable 对象中。

一、操作符介绍

1.concat 首尾相连

const { concat, of } = rxjs;

const source1 = of(1, 2, 3);
const source2 = of(4, 5, 6);
const concated$ = concat(
  source1,
  source2
);

concated$.subscribe(console.log) // 1 2 3 4 5 6

concat的⼯作⽅式:

  1. 从第⼀个Observable对象获取数据,把数据传给下游。
  2. 当第⼀个Observable对象complete之后,concat就会去subscribe第⼆ 个Observable对象获取数据,把数据同样传给下游。
  3. 依次类推,直到最后⼀个Observable完结之后,concat产⽣的 Observable也就完结了。

弹珠图表示:

image.png

concat没有限制参数的个数,可以把任意数量的Observable对象合并。如果一个Observable对象不会完结,那排在后面的Observable对象永远没有上场的机会。

2.merge 先到先得

const { concat, timer, merge } = rxjs;
const { map } = rxjs.operators;

const source1$ = timer(0, 1000).pipe(
  map(x => x + 'A')
)
const source2$ = timer(500, 1000).pipe(
  map(x => x + 'B')
)
const merged$ = merge(source1$, source2$);
merged$.subscribe(
  console.log,
  null,
  () => console.log('complete')
); // 0A 0B 1A 1B 2A 2B

merge会第⼀时间订阅所有的上游Observable,然 后对上游的数据采取“先到先得”的策略,任何⼀个Observable只要有数据推 下来,就⽴刻转给下游Observable对象。

这个看一下弹珠图会比较好理解一些。

image.png

因为merge在第⼀时刻就订阅上游的所有Observable对象,所以,如果 某个上游Observable对象不能完结,并不影响其他Observable对象的数据传 给merge的下游。merge只有在所有的上游Observable都完结的时候,才会 完结⾃⼰产⽣的Observable对象,在上⾯的例⼦中,source1source2和source2都 不会完结,所以由merge组合产⽣的新数据流也不会完结。

  • 同步限流

merge可以有⼀个可选参数,⽤于指定可以同时合并的 Observable对象个数。 假设现在有3个Observable对象,⽽参数的值为2

const { concat, timer,  } = rxjs;
const { map ,merge} = rxjs.operators;

const source1$ = timer(0, 1000).pipe(
  map(x => x + 'A')
)
const source2$ = timer(500, 1000).pipe(
  map(x => x + 'B')
)
const source3$ = timer(1000, 1000).pipe(
  map(x => x + 'C')
)

const merged$ = source1$.pipe(
  merge(source2$, source3$,2)
).subscribe(
  console.log,
  null,
  () => console.log('complete')
);

3.zip 拉链式组合

zip就像是⼀个拉条,上游的 Observable对象就像是拉链的链齿,通过拉条合并,数据⼀定是⼀⼀对应 的。

const { of, concat, timer,zip } = rxjs;
const { map, merge,  } = rxjs.operators;

const source1$ = of(1, 2, 3);
const source2$ = of('a', 'b', 'c');
const zipped$ = zip(source1$, source2$)
zipped$.subscribe(
  console.log,
  null,
  () => console.log('complete')
);
// [1, 'a']
// [2, 'b']
// [3, 'c']

当zip执⾏的时候,它会⽴刻订阅所有的上游Observable,然后开始合并数据,可以看到zip是两条数据流同时进行的, 但是zip会把上游的数据转化为 数组形式, 在同步数据流中如果两个数据源对不上, zip 的执行次数就是最少的那个数据源数量。如下:

const { of, concat, timer,zip } = rxjs;
const { map, merge,  } = rxjs.operators;

const source1$ = of(1, 2, 3);
const source2$ = of( 'c');
const zipped$ = zip(source1$, source2$)
zipped$.subscribe(
  console.log,
  null,
  () => console.log('complete')
);
//  [1, 'c']
  • 异步数据流情况
const { of, interval,concat, timer, zip } = rxjs;
const { map, merge, } = rxjs.operators;

const source1$ = interval(1000);
const source2$ = of('a', 'b', 'c');

const zipped$ = zip(source1$, source2$)
zipped$.subscribe(
  console.log,
  null,
  () => console.log('complete')
);
// [ 0, 'a' ] 
// [ 1, 'b' ] 
// [ 2, 'c' ] 
// complete

之所以输出出现时间间隔,是因为zip要像拉链⼀样做到⼀对⼀咬合。 虽然source2$第⼀时间就吐出了字符串a,但是source1$并没有吐出任何数 据,所以字符串a只能等着,直到1秒钟的时候,source1$吐出了0 时zip就把 两个数据合并为⼀个数据传给下游。字符串 b c 也是如此。

只要任何⼀个上游的Observable完结。zip只要给这个 完结的Observable对象吐出的所有数据找到配对的数据,那么zip就会给下 游⼀个complete信号。

zip的弹珠图,可以更清楚地展⽰这个功能。

image.png

  • 数据积压问题

如果某个上游source1$吐出数据的速度很快,⽽另⼀个上游source2$吐出数据的速度很慢,那zip就 不得不先存储source1$吐出的数据,因为RxJS的⼯作⽅式是“推”, Observable把数据推给下游之后⾃⼰就没有责任保存数据了。被source1$推 送了数据之后,zip就有责任保存这些数据,等着和source2$未来吐出的数 据配对。假如source2$迟迟不吐出数据,那么zip就会⼀直保存source1$没有 配对的数据,然⽽这时候source1$可能会持续地产⽣数据,最后zip积压的 数据就会越来越多,占⽤的内存也就越来越多。 对于数据量⽐较⼩的Observable对象,这样的数据积压还可以忍受, 但是对于超⼤量的数据流,使⽤zip就不得不考虑潜在的内存压⼒问题,zip 这个操作符⾃⾝是解决不了这个问题的。稍后会介绍另一个操作符。

  • zip多个数据流
const { of, interval, concat, timer, zip } = rxjs;
const { map, merge, } = rxjs.operators;

const source1$ = interval(1000);
const source2$ = of('a', 'b', 'c');
const source3$ = of('a', 'b', 'c','d');

const zipped$ = zip(source1$, source2$,source3$)
zipped$.subscribe(
  console.log,
  null,
  () => console.log('complete')
);
// [0, 'a', 'a']
// [1, 'b', 'b']
// [2, 'c', 'c']

根据上面的例子不难看出,吐出数据最少的上游Observable决定了zip产⽣的数据个数。

4.combineLatest 合并最后⼀个数据

combineLatest合并数据流的⽅式是当任何⼀个上游Observable产⽣数据时,从所有输⼊Observable对象中拿最后⼀次产⽣的数据(最新数据), 然后把这些数据组合起来传给下游。注意,这种⽅式和zip不⼀样,zip对上 游数据只使⽤⼀次,⽤过⼀个数据之后就不会再⽤,但是combineLatest可能会反复使⽤上游产⽣的最新数据,只要上游不产⽣新的数据,那combineLatest就会反复使⽤这个上游最后⼀次产⽣的数据。

const { of, interval, concat, timer, zip } = rxjs;
const { map, merge, combineLatest } = rxjs.operators;

const source1$ = timer(500, 1000);
const source2$ = timer(1000, 1000);
const result$ = source1$.pipe(
  combineLatest(source2$)
)
result$.subscribe(
  console.log,
  null,
  () => console.log('complete')
);
// [ 0, 0 ]
// [ 1, 0 ]
// [ 1, 1 ] 
// [ 2, 1 ] 
// [ 2, 2 ] 
// [ 3, 2 ]

可以看到,combineLatest产⽣的Observable对象会⽣成数组数据。每个 数组中元素的个数和上游Observable数量相同,每个元素的下标和对应数 据源在combineLatest中的参数位置⼀致。

并不是说上游产⽣任何⼀个数据都会引发 combineLatest给下游传⼀个数据,只要有⼀个上游数据源还没有产⽣数 据,那么combineLatest也没有数据输出,因为凑不齐完整的数据集合,只能等待。

image.png

source1$产⽣第⼀个数据0时, source2$还没有产⽣数据,所以这个数据0不会引发combineLatest给下游的 数据。

const { of, interval, concat, timer, zip } = rxjs;
const { map, merge, combineLatest } = rxjs.operators;

const source1$ = of('a', 'b', 'c');
const source2$ = of('d', 'e', 'f');
const result$ = source1$.pipe(
  combineLatest(source2$)
)
result$.subscribe(
  console.log,
  null,
  () => console.log('complete')
);
// ['c', 'd']
// ['c', 'e']
// ['c', 'f']

观察上面代码,单独某个上游Observable完结不会让combineLatest产⽣的 Observable对象完结,因为当⼀个Observable对象完结之后,它依然有“最新数据”啊,就是它在完结之前产⽣的最后⼀个数据,combineLatest记着 呢,还可以继续使⽤这个“最新数据”。只有当所有上游Observable对象都完 结之后,combineLatest才会给下游⼀个complete信号,表⽰不会有任何数据更新了。

  • 定制下游数据

combineLatest的最后⼀个参数可以是⼀个函数,这⾥我们称之为 project,project的作⽤是让combineLatest把所有上游的“最新数据”扔给下游 之前做⼀下组合处理,这样就可以不⽤传递⼀个数组下去,可以传递任何 由“最新数据”产⽣的对象。project可以包含多个参数,每⼀个参数对应的 是上游Observable的最新数据,project返回的结果就是combineLatest塞给下 游的结果。

const { of, interval, concat, timer, zip } = rxjs;
const { map, merge, combineLatest } = rxjs.operators;

const source1$ = timer(500, 1000);
const source2$ = timer(1000, 1000);
const project = (a, b) => `${a} ---- ${b}`;

const result$ = source1$.pipe(
  combineLatest(source2$,project)
)
result$.subscribe(
  console.log,
  null,
  () => console.log('complete')
);
// 0 ---- 0
// 1 ---- 0
// 1 ---- 1
// 2 ---- 1
// 2 ---- 2
// 3 ---- 2
// ...

可以看到,combineLatest输出不再是数组形式,⽽是由project决定的字符串。

5.withLatestFrom 提供另一个 observable 的最新值

withLatestFrom的功能类似于combineLatest,但是给下游推送数据只能 由⼀个上游Observable对象驱动。调⽤withLatestFrom的那个Observable对象起到主导数据产⽣节奏的作⽤,作为参数的Observable对象只能贡献数据,不能控制产⽣数据的时机。

const source1$ = timer(0, 2000).pipe(
  map(x => 100 * x)
);
const source2$ = timer(500, 1000);

const result$ = source1$.pipe(
  withLatestFrom(source2$, (a, b) => a + b)
)
result$.subscribe(
  console.log,
  null,
  () => console.log('complete')
);
// 101
// 203
// 305
// 407
// 509
// ...

分析一下上面的代码:

  1. source1$每隔2秒钟产⽣⼀个数据,通过map的映射,实际产⽣ 的数据序列为0、100、200...
  2. source2$从第500毫秒时刻开始,每隔1秒钟产⽣⼀个从0开始的递增数字序列。
  3. result$source1$调⽤withLatestFrom产⽣,第⼀个参数是source2$,这 样source2$的数据会是result$的⼀个输⼊。注意,withLatestFrom第⼆个函数,功能和zip以及combineLatest的可选参数⼀样,⽤于定制产⽣的数据对象形式。
  4. 在第0毫秒时刻,source1$吐出数据100,source2$没有吐出数据, 所以没有给下游产⽣数据。
  5. 在第500毫秒时刻,source2$吐出数据0,但是source2$并不直接触发给下游传递数据,所以依然没有给下游产⽣产⽣数据。
  6. 在第1500毫秒时刻,source2$吐出数据1,同样不会给下游产⽣数据。
  7. 在第2000毫秒时刻,source1$吐出数据100,这个数据会加上source2$吐出的最后⼀个数据1,产⽣传给下游的数据101。
  8. 在第2500毫秒时刻,source2$吐出数据2,不会给下游产⽣数据。
  9. 在第3500毫秒时刻,source2$吐出数据3,不会给下游产⽣数据。
  10. 在第4000毫秒时刻,source1$吐出数据200,这个数据加上source2$吐出的最后⼀个数据3,产⽣传给下游的的数据203。

下图是 withLatestFrom 的弹珠图。

image.png

6.race 使用首先发出值的 observable

race就是多个Observable对象在⼀起,看谁最先产⽣数据。第⼀个吐出数据的Observable对象就是传递给下游的Observable,race产⽣的Observable就会完全采⽤第⼀个吐出的数据,其余的输⼊Observable对象则会被退订⽽抛弃。不过, 所有的输⼊Observable对象地位都是平等的,所以参数的顺序没有关系。

const { of, interval, concat, timer, zip } = rxjs;
const { map, merge, withLatestFrom,race } = rxjs.operators;

const source1$ = timer(0, 2000).pipe(
  map(x => x + 'a')
)
const source2$ = timer(500, 1000).pipe(
  map(x => x + 'b')
)
const winner$ = source1$.pipe(
  race(source2$)
)

winner$.subscribe(
  console.log,
  null,
  () => console.log('complete')
);

// 0a
// 1a
// 2a
// 3a
// ...

race看的可不是产⽣数据的频率快慢,⽽是看哪⼀个Observable 对象最先产⽣第⼀个数据。虽然source2$产⽣数据的频率快,但是它产⽣ 第⼀个数据要⽐source1$晚500毫秒,就因为这500毫秒,source2$失去了先 机,race就会退订source2$,完全从source1$中拿数据。

image.png

5.startWith 在开头添加要发送的元素

startWith只有实例操作符的形式,其功能是让⼀个Observable对象在被 订阅的时候,总是先吐出指定的若⼲个数据。

const { of, interval, concat, timer, zip } = rxjs;
const { map, merge, withLatestFrom, race, startWith } = rxjs.operators;

const original$ =timer(0, 1000);
const result$ = original$.pipe(
  startWith('start')
)

result$.subscribe(
  console.log,
  null,
  () => console.log('complete')
);
// start
// 0
// 1
// 2
// 3
// ...

当被订阅的时候会⽴刻吐出startWith的参 数,然后就该上游Observable对象上场产⽣数据了。

startWith的⼀点不⾜是所有参数都是同步吐出的,如果需要异步吐出 参数,那还是只能利⽤concat。

5.forkJoin Observable对象的最后⼀个数据

可以接受多个Observable对象作为参数,forkJoin产⽣的Observable对象也很有特点,它只会产⽣⼀个数据,因为它会等待所有参数Observable对象的最后⼀个数据,也就是说,只有当所有Observable对象都完结,确定不会有新的数据产⽣的时候,forkJoin就会把所有输⼊Observable对象产⽣的最后⼀个数据合并成给下游唯⼀的数 据。 所以说,forkJoin就是RxJS界的Promise.all。

const { of, interval, forkJoin,timer, zip } = rxjs;
const { take , concat, map, merge, withLatestFrom,  race, startWith } = rxjs.operators;


const source1$ = interval(1000).pipe(
  map(x => x + 'a'),
  take(1)
)
const source2$ = interval(1000).pipe(
  map(x => x + 'b'),
  take(3)
)

const concated$ = forkJoin(source1$, source2$);


concated$.subscribe(
  console.log,
  null,
  () => console.log('complete')
);

//  ['0a', '2b']
//  complete

二、高阶操作符

⾼阶Observable打开了⼀扇⼤门,⽤Observable来管理多个Observable 对象。

数据流虽然管理的是数据,数据流⾃⾝也可以认为是⼀种数据,既然 数据可以⽤Observable来管理,那么数据流本⾝也可以⽤Observable来管 理,让需要被管理的Observable对象成为其他Observable对象的数据,⽤现 成的管理Observable对象的⽅法来管理Observable对象,这就是⾼阶 Observable的意义。

⾼阶Observable的本质是⽤管理数据的⽅式来管 理多个Observable对象。

1.concatAll 收集 observables,当上一个完成时订阅下一个。

concat是把所有输⼊的Observable⾸尾相连组合在⼀ 起,concatAll做的事情也⼀样,只不过concatAll只有⼀个上游Observable对 象,这个Observable对象预期是⼀个⾼阶Observable对象,concatAll会对其 中的内部Observable对象做concat的操作。

const {  of , create, interval } = rxjs;
const { repeat, take ,map, concatAll} = rxjs.operators;
const ho$ = interval(1000)
.pipe(
    take(2),map(x => interval(1500).pipe(
    map(y => x+':'+y),
    take(2)
    ))
)
const concated$ = ho$.pipe(
    concatAll()
) // 0:0 0:1 1:0 1:1

concated$.subscribe(x => console.log(x));

image.png

这是因为concatAll⾸先会订阅上游产⽣的第⼀个内部Observable对象, 抽取其中的数据,然后,只有当第⼀个Observable对象完结的时候,才会 去订阅第⼆个内部Observable对象。也就是说,虽然⾼阶Observable对象已 经产⽣了第⼆个Observable对象,不代表concatAll会⽴刻去订阅它,因为这 个Observable对象是懒执⾏,所以不去订阅⾃然也不会产⽣数据,最后⽣ 成1:0的时间也就被推迟到产⽣0:1之后。

和concat⼀样,如果前⼀个内部Observable没有完结,那么concatAll就 不会订阅下⼀个内部Observable对象,这导致⼀个问题,如果上游的⾼阶 Observable对象持续不断产⽣Observable对象,但是这些Observable对象又 异步产⽣数据,以⾄于concatAll合并的速度赶不上上游产⽣新的Observable 对象的速度,这就会造成Observable的积压。

2.mergeAll 收集并订阅所有的 observables。

mergeAll对内部Observable的订阅策略和concatAll不同,mergeAll只要 发现上游产⽣⼀个内部Observable就会⽴刻订阅,并从中抽取收据。

const { interval } = rxjs;
const { take, map, mergeAll } = rxjs.operators;

const ho$ = interval(1000)
  .pipe(
    take(2)
    , map(x => interval(1500).pipe(
      map(y => x + ':' + y),
      take(2))
    )
  )


const concated$ = ho$.pipe(
  mergeAll()
)

concated$.subscribe(res => {
  console.log('res', res)
})
// 0:0
// 1:0
// 0:1
// 1:1

image.png

在上图中,第⼆个内部Observable产⽣的数据1:0会出现在第⼀个内部 Observable产⽣的数据0:1之前。

3.zipAll 一对一。

zipAll 和 zip 工作的方式大致相同,只是 zipAll 接受的是 Observable

const { interval } = rxjs;
const { take, map, zipAll } = rxjs.operators;

const ho$ = interval(1000).pipe(
  take(2),
  map(x => interval(1500).pipe(
    map(y => x + ':' + y),
    take(2)
  ))
)

const concated$ = ho$.pipe(
  zipAll()
)

concated$.subscribe(res => {
  console.log('res', res)
})
// ['0:0', '1:0']
// ['0:1', '1:1']

两个内部Observable产⽣的数据⼀对⼀配对出现。

4.# combineAll 一对一。

combineAll就是处理⾼阶Observable的combineLatest。

const { interval } = rxjs;
const { take, map, combineAll  } = rxjs.operators;

const ho$ = interval(1000).pipe(
  take(2),
  map(x => interval(1500).pipe(
    map(y => x + ':' + y),
    take(2)
  ))
)


const concated$ = ho$.pipe(
  combineAll()
)

concated$.subscribe(res => {
  console.log(res)
})

// ['0:0', '1:0']
// ['0:1', '1:0']
// ['0:1', '1:1']

combineAll和zipAll⼀样,必须上游⾼阶Observable完结之后才能开始 给下游产⽣数据,因为只有确定了作为输⼊的内部Observable对象的个 数,才能拼凑出第⼀个传给下游的数据。

如果combineAll的上游⾼阶Observable永不完结,即使部分内部 Observable已经产⽣了数据,combineAll也不会有机会产⽣任何数据。

5.switch 切换输⼊Observable。

switch的含义就是“切换”,总是切换到最新的内部Observable对象获取 数据。每当switch的上游⾼阶Observable产⽣⼀个内部Observable对象,switch都会⽴刻订阅最新的内部Observable对象上,如果已经订阅了之前的 内部Observable对象,就会退订那个过时的内部Observable对象,这个“⽤ 上新的,舍弃旧的”动作,就是切换。

注意:RxJS5 中叫switch,由于与Javascript保留字冲突,RxJS 6中对以下运算符名字做了修改:do -> tap, catch ->catchError, switch -> switchAll, finally -> finalize。

const { interval } = rxjs;
const { take, map, switchAll } = rxjs.operators;

const ho$ = interval(1000).pipe(
  take(2),
  map(x => interval(1500).pipe(
    map(y => x + ':' + y),take(2)
  ))
)

const result$ = ho$.pipe(
  switchAll()
)

result$.subscribe(res => {
  console.log(res)
})

// 1:0 
// 1:1

image.png

现在按照弹珠图来分析一下:

switch⾸先订阅了第⼀个内部Observable对象,但是这个内部对象还没 来得及产⽣第⼀个数据0:0,第⼆个内部Observable对象就产⽣了,这时 候switch就会做切换动作,切换到第⼆个内部Observable上,因为之后没有 新的内部Observable对象产⽣,switch就会⼀直从第⼆个内部Observable对 象获取数据,于是最后得到的数据就是1:0和1:1。

注意,这和race有区别,并不是和race⼀样谁先产生第一个Observable就一直是他,switch是谁产生了Observable谁就⼀直输出,直到另一个Observable产生。

我们对代码做⼀些修改,让它产⽣3个内部Observable对象,就会把这 个过程看得更清楚:

const { interval } = rxjs;
const { take, map, switchAll } = rxjs.operators;

const ho$ = interval(1000).pipe(
  take(3),
  map(x => interval(700).pipe(
    map(y => x + ':' + y),
    take(2)
  ))
)

const result$ = ho$.pipe(
  switchAll()
)

result$.subscribe(res => {
  console.log(res)
})

// 0:0
// 1:0
// 2:0
// 2:1

image.png

第⼀个Observable对象有机会产⽣数据0: 0,但是在第⼆个数据0:1产⽣之前,第⼆个内部Observable对象产⽣,这 时发⽣切换,第⼀个内部Observable就退场了。同样,第⼆个内部 Observable只有机会产⽣⼀个数据1:0,然后第三个内部Observable对象产 ⽣,之后没有新的内部Observable对象产⽣,所以第三个Observable对象的 两个数据2:0和2:1都进⼊了下游。

值得注意的是switch产⽣的Observable对象何时完结,这个对象完结基 于两个条件:

  • 上游⾼阶Observable已经完结。
  • 当前内部Observable已经完结。

只满⾜上⾯其中⼀个条件,并不会让switch产⽣的Observable对象完结。

6.exhaust 耗尽。

exhaust的含义就是“耗尽”,这个操作符的意思是,在耗尽当前内部 Observable的数据之前不会切换到下⼀个内部Observable对象。

const { interval } = rxjs;
const { take, map, exhaust } = rxjs.operators;

const ho$ = interval(1000).pipe(
  take(3),
  map(x => interval(700).pipe(
    map(y => x + ':' + y),
    take(2)
  ))
)

const result$ = ho$.pipe(
  exhaust()
)

result$.subscribe(res => {
  console.log(res)
})

// 0:0
// 0:1
// 2:0
// 2:1

exhaust⾸先从第⼀个内部Observable对象获取数据, 然后再考虑后续的内部Observable对象。第⼆个内部Observable⽣不逢时, 当它产⽣的时候第⼀个内部Observable对象还没有完结,这时候exhaust会 直接忽略第⼆个Observable对象,甚⾄不会去订阅它;第三个内部 Observable对象会被订阅并提取数据,是因为在它出现之前,第⼀个内部 Observable对象已经完结了。

image.png

和switch⼀样,exhaust产⽣的Observable对象完结前提是,最新的内部 Observable对象完结⽽且上游⾼阶Observable对象完结

小结

concat对于concatAll,merge对于mergeAll,zip对于zipAll,combineLatest对 于combineAll,主要的区别只是作为输⼊的Observable对象的形式变化。不 带All的操作符输⼊Observable对象是以操作符调⽤主体对象或者函数参数 形式出现,带All的操作符输⼊Observable对象是以上游⾼阶Observable对象 产⽣的内部Observable对象形式出现。

不同合并⽅法的 区别在于⽤何种策略把上游多个Observable对象中数据转⼿给下游,例 如,concat的策略是让上游Observable对象的数据依次⾸尾相连,merge是 任何数据先来先进⼊下游,zip则要保证所有上游Observable对象公平,数 据要⼀⼀对应。

参考《深入浅出RxJs》——程墨