30 天精通 RxJS (10): Observable Operator - combineLatest, withLatestFrom, zip

非同步最难的地方在于,当有多个非同步行为同时触发且相互依赖,这时候咱们要处理的逻辑跟状态就会变得极其复杂,甚至程序极可能会在完成的一两天后就成了 Legacy Code(遗留代码)。javascript

昨天咱们最后讲到了 merge 的用法,它的逻辑就像是 OR(||)同样,能够把多个 observable 合而且同时处理,当其中任合一个 observable 送出元素时,咱们都作相同的处理。java

今天咱们要讲的三个 operators 则像是 AND(&&) 逻辑,它们都是在多个元素送进来时,只输出一个新元素,但各自的行为上仍有差别,须要读者花点时间思考,建议在头脑清醒时阅读本篇文章。编辑器

Operators

combineLatest

首先咱们要介绍的是 combineLatest,它会取得各个 observable 最后送出的值,再输出成一个值,咱们直接看示例会比较好解释。ui

var source = Rx.Observable.interval(500).take(3);
var newest = Rx.Observable.interval(300).take(6);

var example = source.combineLatest(newest, (x, y) => x + y);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});
// 0
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// complete复制代码

JSBin | JSFiddlespa

你们第一次看到这个 output 应该都会很困惑,咱们直接来看 Marble Diagram 吧!.net

source : ----0----1----2|
newest : --0--1--2--3--4--5|

    combineLatest(newest, (x, y) => x + y);

example: ----01--23-4--(56)--7|复制代码

首先 combineLatest 能够接收多个 observable,最后一个参数是 callback function,这个 callback function 接收的参数数量跟合并的 observable 数量相同,依照示例来讲,由于咱们这里合并了两个 observable 因此后面的 callback function 就接收 x, y 两个参数,x 会接收从 source 发送出来的值,y 会接收从 newest 发送出来的值。code

最后一个重点就是必定会等两个 observable 都曾有送值出来才会呼叫咱们传入的 callback,因此这段程式是这样运行的ip

  • newest 送出了 0,但此时 source 并无送出过任何值,因此不会执行 callback
  • source 送出了 0,此时 newest 最后一次送出的值为 0,把这两个数传入 callback 获得 0
  • newest 送出了 1,此时 source 最后一次送出的值为 0,把这两个数传入 callback 获得 1
  • newest 送出了 2,此时 source 最后一次送出的值为 0,把这两个数传入 callback 获得 2
  • source 送出了 1,此时 newest 最后一次送出的值为 2,把这两个数传入 callback 获得 3
  • newest 送出了 3,此时 source 最后一次送出的值为 1,把这两个数传入 callback 获得 4
  • source 送出了 2,此时 newest 最后一次送出的值为 3,把这两个数传入 callback 获得 5
  • source 结束,但 newest 还没结束,因此 example 还不会结束。
  • newest 送出了 4,此时 source 最后一次送出的值为 2,把这两个数传入 callback 获得 6
  • newest 送出了 5,此时 source 最后一次送出的值为 2,把这两个数传入 callback 获得 7
  • newest 结束,由于 source 也结束了,因此 example 结束。

不论是 source 仍是 newest 送出值来,只要另外一方曾有送出过值(有最后的值),就会执行 callback 并送出新的值,这就是 combineLatest。内存

combineLatest 很经常使用在运算多个因子的结果,例如最多见的 BMI 计算,咱们身高变更时就拿上一次的体重计算新的 BMI,当体重变更时则拿上一次的身高计算 BMI,这就很适合用 combineLatest 来处理!get

zip

在讲 withLatestFrom 以前,先让咱们先来看一下 zip 是怎么运行的,zip 会取每一个 observable 相同顺位的元素并传入 callback,也就是说每一个 observable 的第 n 个元素会一块儿被传入 callback,这里咱们一样直接用示例讲解会比较清楚

var source = Rx.Observable.interval(500).take(3);
var newest = Rx.Observable.interval(300).take(6);

var example = source.zip(newest, (x, y) => x + y);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});
// 0
// 2
// 4
// complete复制代码

JSBin | JSFiddle

Marble Diagram 长这样

source : ----0----1----2|
newest : --0--1--2--3--4--5|
    zip(newest, (x, y) => x + y)
example: ----0----2----4|复制代码

以咱们的示例来讲,zip 会等到 source 跟 newest 都送出了第一个元素,再传入 callback,下次则等到 source 跟 newest 都送出了第二个元素再一块儿传入 callback,因此运行的步骤以下:

  • newest 送出了第一个0,但此时 source 并无送出第一个值,因此不会执行 callback。
  • source 送出了第一个0,newest 以前送出的第一个值为 0,把这两个数传入 callback 获得 0
  • newest 送出了第二个1,但此时 source 并无送出第二个值,因此不会执行 callback。
  • newest 送出了第三个2,但此时 source 并无送出第三个值,因此不会执行 callback。
  • source 送出了第二个1,newest 以前送出的第二个值为 1,把这两个数传入 callback 获得 2
  • newest 送出了第四个3,但此时 source 并无送出第四个值,因此不会执行 callback。
  • source 送出了第三个2,newest 以前送出的第三个值为 2,把这两个数传入 callback 获得 4
  • source 结束 example 就直接结束,由于 source 跟 newest 不会再有对应顺位的值

zip 会把各个 observable 相同顺位送出的值传入 callback,这很常拿来作 demo 使用,好比咱们想要间隔 100ms 送出 'h', 'e', 'l', 'l', 'o',就能够这么作

var source = Rx.Observable.from('hello');
var source2 = Rx.Observable.interval(100);

var example = source.zip(source2, (x, y) => x);复制代码

这里的 Marble Diagram 就很简单

source : (hello)|
source2: -0-1-2-3-4-...
        zip(source2, (x, y) => x)
example: -h-e-l-l-o|复制代码

这里咱们利用 zip 来达到本来只能同步送出的资料变成了非同步的,很适合用在创建示范用的资料。

建议你们日常没事不要乱用 zip,除非真的须要。由于 zip 必须 cache 住还没处理的元素,当咱们两个 observable 一个很快一个很慢时,就会 cache 很是多的元素,等待比较慢的那个 observable。这颇有可能形成内存相关的问题!

withLatestFrom

withLatestFrom 运行方式跟 combineLatest 有点像,只是他有主从的关系,只有在主要的 observable 送出新的值时,才会执行 callback,附随的 observable 只是在背景下运行。让咱们看一个例子

var main = Rx.Observable.from('hello').zip(Rx.Observable.interval(500), (x, y) => x);
var some = Rx.Observable.from([0,1,0,0,0,1]).zip(Rx.Observable.interval(300), (x, y) => x);

var example = main.withLatestFrom(some, (x, y) => {
    return y === 1 ? x.toUpperCase() : x;
});

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});复制代码

JSBin | JSFiddle

先看一下 Marble Diagram

main   : ----h----e----l----l----o|
some   : --0--1--0--0--0--1|

withLatestFrom(some, (x, y) =>  y === 1 ? x.toUpperCase() : x);

example: ----h----e----l----L----O|复制代码

withLatestFrom 会在 main 送出值的时候执行 callback,但请注意若是 main 送出值时 some 以前没有送出过任何值 callback 仍然不会执行!

这里咱们在 main 送出值时,去判断 some 最后一次送的值是否是 1 来决定是否要切换大小写,执行步骤以下

  • main 送出了 h,此时 some 上一次送出的值为 0,把这两个参数传入 callback 获得 h
  • main 送出了 e,此时 some 上一次送出的值为 0,把这两个参数传入 callback 获得 e
  • main 送出了 l,此时 some 上一次送出的值为 0,把这两个参数传入 callback 获得 l
  • main 送出了 l,此时 some 上一次送出的值为 1,把这两个参数传入 callback 获得 L
  • main 送出了 o,此时 some 上一次送出的值为 1,把这两个参数传入 callback 获得 O

withLatestFrom 很经常使用在一些 checkbox 型的功能,例如说一个编辑器,咱们开启粗体后,打出来的字就都要变粗体,粗体就像是 some observable,而咱们打字就是 main observable。

今日小结

今天介绍了三个合并用的 operators,这三个 operators 的 callback 都会依照合并的 observable 数量来传入参数,若是咱们合并了三个 observable,callback 就会有三个参数,而无论合并几个 observable 都会只会回传一个值。

这几个 operators 须要花比较多的时间思考,读者们不用硬记他的运行行为,只要稍微记得有这些 operators 能够用就能够了。等到真的要用时,再从新回来看他们的运行方式作选择。

不知道读者们今天有没有收获呢? 若是有任何问题,欢迎在下方留言给我,谢谢!

相关文章
相关标签/搜索