You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RXJS中scan仅返回单个元素,期望返回多个的问题排查

嘿,我来帮你理清楚这个问题!你遇到的核心问题其实是RxJS里switchMap操作符的特性在“搞鬼”,导致你最终只拿到了包含最后一个元素的数组。

为什么会这样?

Observable.range(0, 10)会快速连续吐出10个值,但你的put方法是异步的IndexedDB请求,需要一点时间才能完成。而switchMap的行为是:每当源Observable发射新值时,它会立刻取消之前还在运行的Observable订阅,转而订阅新的那个。

也就是说,当range吐出第2个值时,第1个put请求可能还没跑完,switchMap就直接把它的订阅取消了——这意味着第1个请求的结果永远不会传到后面的scan里。以此类推,前面9个put请求全被中途取消了,只有最后一个请求顺利完成,结果被scan收集到,所以数组里自然只有1个元素。

看你的日志也能验证这点

从你贴的日志片段里能明显看到:

2 Jean Gibbs
3 Jean Gibbs
put Jean Gibbs
put.finished Jean Gibbs
2 Chris Garrett
3 Chris Garrett
put Chris Garrett
put.finished Chris Garrett
2 Todd Moss
3 Todd Moss
put Todd Moss
put.finished Todd Moss
val Todd Moss
acc.count 0
4 1
5 Todd Moss
length 1

前面的Jean Gibbs和Chris Garrett的put虽然执行完了,但它们的结果根本没进入scan环节,只有最后一个Todd Moss的结果被scan接收到了。

怎么解决?

根据你的需求(要收集所有put完成后的结果到数组),把switchMap换成**concatMap**就好:

concatMap的特性是必须等前一个Observable完全完成,才会订阅下一个,这样所有put请求会按顺序依次执行,每个请求的结果都会被传递到scan里,最终就能得到包含10个元素的数组了。

修改后的核心代码片段:

return Observable.range(0, numberOfOwners)
  .do(counter => console.log('1 ' + counter)) // 这里你原来的do没打日志,建议加上方便调试
  .map(counter => NameService.GeneratePersonName())
  .do(val => console.log('2 ' + val))
  .map(personName => OwnerModel.GenerateModel(personName, NumberService.GenerateRandomNumber(60), isUser, game))
  .do(owner => console.log('3 ' + (owner as OwnerModel).name))
  .concatMap(owner => this.put(owner)) // 把switchMap换成concatMap
  .scan((acc, val) => {
    console.log('val ' + val.name);
    console.log('acc.count ' + acc.length);
    acc.push(val);
    return acc;
  } , [])
  .do(owners => console.log('4 ' + owners.length))
  .do(owners => console.log('5 ' + owners[0].name));

另外还有个可选方案

如果你不需要按顺序执行请求,想让所有put并行跑,最后一次性拿到所有结果,可以用mergeMap配合toArray:

return Observable.range(0, numberOfOwners)
  .map(counter => NameService.GeneratePersonName())
  .map(personName => OwnerModel.GenerateModel(personName, NumberService.GenerateRandomNumber(60), isUser, game))
  .mergeMap(owner => this.put(owner)) // 并行执行所有put请求
  .toArray(); // 等所有请求都完成后,把结果打包成数组

不过toArray()要等源Observable完全结束才会吐出数组,而scan是每收到一个结果就吐出一次更新后的数组,你可以根据自己的调试需求或者业务场景来选。

最后提个小细节

你原来的.do(counter => '1 ' + counter)其实没输出任何日志,应该改成.do(counter => console.log('1 ' + counter)),这样才能看到每个counter的调试信息哦。

内容的提问来源于stack exchange,提问作者Simon Houlton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 10:07:16