RxJS中.reduce(...)返回空数组但.scan(...)正常,原因是什么?
为什么RxJS里
reduce替代scan后返回空数组,观察者收不到数据? 嘿,这个问题我之前踩过坑!核心原因其实是reduce和scan这俩操作符的发射时机完全不同,刚好戳中了你当前数据流的特性。
先搞懂两者的核心差异
scan是「实时发射中间累计结果」:每次上游Observable发出一个新值,它就会用累加器函数把当前值和之前的累计结果合并,然后立刻把这个更新后的结果发射给观察者。哪怕上游流还在持续发送数据,你的dataSub$观察者每次都能拿到最新的数组。reduce是「等待流完成后发射最终结果」:它会默默收集上游所有发出的值,只有当上游Observable**明确调用complete()**之后,才会把最终的累计结果一次性发射出去。如果你的上游流是一个「不会自动结束」的数据流(比如持续的事件流、一直存活的Subject、WebSocket推送这类),那reduce就会一直等下去,永远不会发射任何结果——你的观察者自然收不到东西,看起来就像返回空数组一样。
结合你的场景分析
你提到用了mergeMap来过滤元素,那大概率你的上游数据流(或者mergeMap内部的流)是不会自动完成的。比如:
- 如果
mergeMap是用来处理一个持续产生数据的源(比如用户输入、实时更新的后端数据),那这个流永远不会触发complete; - 或者你没有手动给这个流添加结束的逻辑(比如用
take()、takeUntil()这类操作符让流终止)。
这种情况下,scan会每次处理完过滤后的元素就发射累计数组,而reduce一直在等流结束,所以完全没输出。
怎么解决?
- 如果你的需求就是每次有新元素就更新数组并通知观察者,那
scan就是最适合的选择,没必要换成reduce; - 如果确实需要等所有元素处理完后再拿到最终数组,那你得确保上游流会触发
complete:比如给mergeMap内部的流加take(1)(如果只需要处理一次),或者用takeUntil()监听一个结束信号(比如用户点击「完成」按钮),让reduce能收到结束信号并发射最终结果。
举个简单的代码对比例子:
// 用scan:每次发射中间结果 from([1,2,3]).pipe( scan((acc, val) => [...acc, val], []) ).subscribe(arr => console.log(arr)); // 输出:[1], [1,2], [1,2,3] // 用reduce:只有流完成后才发射最终结果 from([1,2,3]).pipe( reduce((acc, val) => [...acc, val], []) ).subscribe(arr => console.log(arr)); // 输出:[1,2,3](只有当from的流完成后才会输出)
内容的提问来源于stack exchange,提问作者Zach LeFevre
相关产品推荐
相关产品推荐

