RxJS:如何映射流的最后一个元素并保留前序元素?
没问题!你的核心问题是原代码里用了last()直接过滤掉了前一个值A,只保留了最后一个B并转成C。要保留A同时输出处理后的C,我们可以通过共享api流的订阅,把它拆成两个分支处理:一个分支输出除最后一个外的所有值(也就是A),另一个分支处理最后一个值成C,再把两个分支的结果合并起来。而且完全不需要局部变量,用RxJS的链式操作就能搞定。
通用解决方案(适配任意数量的前序元素)
stream$ .debounceTime(100) .mergeMap(element => api(stream$, ...) // 发射A、B(或更多元素)的流 .pipe( publish(shared$ => merge( // 分支1:输出所有非最后元素(这里就是A) shared$.pipe(takeUntil(shared$.pipe(last()))), // 分支2:取最后元素B,映射成C shared$.pipe(last(), map(lastElement => f(lastElement.result))) ) ) ) )
代码逻辑解释
publish(shared$ => ...):把api流的订阅共享给内部两个分支,避免重复触发api请求(这是关键,不然会调用两次api)。- 第一个分支
shared$.pipe(takeUntil(shared$.pipe(last()))):last()会等待api流完成并发出最后一个值,takeUntil会在这个最后值发出前,把所有之前的元素(比如A)都发射出来,一旦last()发出值,这个分支就停止。 - 第二个分支
shared$.pipe(last(), map(...)):和你原代码逻辑一致,取最后一个元素B,映射成目标值C。 merge(...):把两个分支的结果合并,最终流就会依次发射A、C。
简化方案(仅当api流固定发射2个元素时可用)
如果能确定api流只会发射A、B两个值,也可以用索引判断的方式,代码更简洁:
stream$ .debounceTime(100) .mergeMap(element => api(stream$, ...).pipe( // 索引从0开始,第二个元素(B)索引为1,转成C;第一个元素(A)直接输出 map((val, idx) => idx === 1 ? f(val.result) : val) ) )
不过这个方案依赖元素数量固定为2,后续如果api流元素数量变化,逻辑会失效,所以更推荐前面的通用方案。
内容的提问来源于stack exchange,提问作者ᴘᴀɴᴀʏɪᴏᴛɪs
相关产品推荐
相关产品推荐

