之前的文章中我们介绍过了 scan 操作符,和 JavaScript 数组的 reduce 函数很像。其实在 RxJS 中也有 reduce 操作符。可是既然已经有了 scan ,为什么还会有 reduce?当然是有差别的,reduce 操作符必须要等到上游事件流发生 complete 事件后才会输出。比如下面的程序是永远看不到输出的:
combineLatest(
timer$,
input$,
(timeValue, inputValue) => ({ count: timeValue, input: inputValue })
)
.pipe(
reduce((acc, curr) => acc + 1, 0),
tap(console.log)
)
因为上游事件流不会产生 complete 事件。再来看看下面的代码:
combineLatest(
timer$,
input$,
(timeValue, inputValue) => ({ count: timeValue, input: inputValue })
)
.pipe(
takeWhile(data => data.count < 3),
reduce((acc, curr) => acc + 1, 0),
tap(console.log)
)
这段程序会输出吗?当然,因为 taikeWhile 操作符的关系,当上游的事件对象的 count 属性变成 3 时,就会产生 complete 事件,reduce 操作就会把累积的数字输出。因此当我们需要知道累积函数每一步的值时就用 scan 操作符,如果只对最终结果感兴趣就用 reduce 操作符,但事件流一定是可以正常结束的才可以。
下面我们来看看另一对有同样行为又有微妙不同的操作符。combineLatest 和 withLatestFrom 操作符。让我们精简下代码,这样更能看清它们的区别,首先看下 combineLatest 操作符:
const combineTimerAndInput$ = combineLatest(
timer$,
input$,
(timeValue, inputValue) => ({ count: timeValue, input: inputValue })
)
const subscription = combineTimerAndInput$
.subscribe(
x => console.log(x),
err => console.log(err),
() => console.log("complete")
);
之前的文章已经提到,combineLatest 操作符一定要等到被 combine 的所有流都产生事件才会输出,我们这里是两个流。我们这样操作看下效果:
- 点击开始按钮;
- 等几秒,在文本框输入内容;
- 等几秒,点击暂停按钮;
- 在文本框输入内容;
动图如下:
总结一下现象:一开始只有定时器产生事件,因此没有输出,当在文本框开始输入内容时,文本流产生了事件。这样两个流都产生了事件,必然开始有输出。但当我们停止在文本框输入时,文本流就没有事件产生了,定时器还在不断产生事件,按理说应该停止输出了,但 combineLatest 依然输出。同样的,当我们点击了暂停按钮,定时器流就没有事件产生了,文本流这是也没有事件产生,因此是没有输出的,但当我们保持暂停,在文本框输入内容时,combineLatest 又会开始输出。这就是 combineLatest 的工作机制。只要被组合的流中都有过值,那么只要被组合的流中有一个产生新事件,其他流都以最后一次产生的事件作为新事件共同输出。接下来看看 withLatestFrom 操作符,大家都会对这段解释有更清晰的认识。
代码如下:
const subscription = timer$
.pipe(
withLatestFrom(input$, (timeValue, inputValue) => ({
count: timeValue,
input: inputValue
})),
)
.subscribe(
x => console.log(x),
err => console.log(err),
() => console.log("complete")
);
首先,withLatestFrom 操作符是个对象方法,不像 combineLatest 是个静态方法(combineLatest 在之前 RxJS 版本中也是对象方法)。其次我们看到 withLatestFrom 的用法和 combineLatest 是一致的。测试步骤如下:
- 点击开始按钮;
- 等几秒,在文本框输入内容;
- 等几秒,点击暂停按钮;
- 在文本框输入内容;
- 再次点击开始按钮;
动图如下:
前面三步的效果和 combineLatest 是一样的,到了第四步在文本框输入内容时,没有了输出;第五步又开始有了输出。总结一下:其实从 withLatestFrom 是对象函数就能看出,它是需要有事件流作为基础的,combineLatest 则不然,是用来创造事件流的。也就是说,当上游事件流中没有事件产生时,withLatestFrom 是不会有输出的。符如其名,withLatestFrom 会把上游事件流中的事件和接收参数中的事件流的最后一个值一起进行处理后输出。
如有任何问题,请关注微信公众号“读一读我”。