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

转载

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

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

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

Operators

combineLatest

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

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 | JSFiddle

大家第一次看到这个 output 应该都会很困惑,我们直接来看 Marble Diagram 吧!

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 发送出来的值。

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

  • 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 来处理!

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 可以用就可以了。等到真的要用时,再重新回来看他们的运作方式做选择。

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容