RxJs-concatMap替代方案,将所有内容都放在两者之间

RxJs-concatMap替代方案,将所有内容都放在两者之间,rxjs,Rxjs,我试图找到一个行为类似于concatMap的操作符,但在两者之间删除所有内容。例如,concatMap执行以下操作: 下一个 开始处理 下一个b 下一个c 处理完一个 开始处理b 完成处理b 开始处理c 完成处理c 取而代之的是,我正在寻找一种机制,它将丢弃b,因为c已经出现了: 下一个 开始处理 下一个b 下一个c 处理完一个 (跳过b) 开始处理c 完成处理c 有关更详细的示例,请参见此要点:在a上执行mergeMap之后,您可以尝试在b上使用race方法 我看起来像这样: a.pi

我试图找到一个行为类似于
concatMap
的操作符,但在两者之间删除所有内容。例如,concatMap执行以下操作:

  • 下一个
  • 开始处理
  • 下一个b
  • 下一个c
  • 处理完一个
  • 开始处理b
  • 完成处理b
  • 开始处理c
  • 完成处理c
取而代之的是,我正在寻找一种机制,它将丢弃
b
,因为
c
已经出现了:

  • 下一个
  • 开始处理
  • 下一个b
  • 下一个c
  • 处理完一个
  • (跳过b)
  • 开始处理c
  • 完成处理c

有关更详细的示例,请参见此要点:

a
上执行
mergeMap
之后,您可以尝试在
b
上使用
race
方法

我看起来像这样:

a.pipe(
  mergeMap(AResult => {
     // store aResult
     return race(b,c)
  }
).subscribe(
   finalResult => {
      // final result corresponding to either b or c
   }
)

如果您在
a

之后要执行的调用数已经定义,那么这将起作用。这是我能够做出的最简单的解决方案:

const source = new Subject();
const start = new Date();
const mockDelayedObs = val => of(val).pipe(delay(1200));

source.pipe(
  multicast(
    new ReplaySubject(1),
    subject => {
      let lastValue;

      return subject.pipe(
        filter(v => v !== lastValue),
        exhaustMap(v => {
          lastValue = v;
          return mockDelayedObs(v);
        }),
        take(1),
        repeat(),
      );
    }
  ),
)
.subscribe(v => {
  console.log(new Date().getTime() - start.getTime(), v);
});

setTimeout(() => source.next(0), 0);
setTimeout(() => source.next(1), 500);
setTimeout(() => source.next(2), 1000);
setTimeout(() => source.next(3), 1500);
setTimeout(() => source.next(4), 1800);
setTimeout(() => source.next(5), 4000);
现场演示:

行动顺序如下:

next 0
start handling 0
next 1
next 2
finish handling 0
start handling 2
next 3
next 4
finish handling 2
start handling 4
finish handling 4
start handling 5
finish handling 4
因此,只会打印0、2、4和5


这在没有
多播
操作符的情况下也可以工作,但我希望避免泄漏状态变量。似乎没有它们是不完全可能的,所以只有一个
lastValue
。此变量仅用于在使用
repeat()

重新订阅同一链后,忽略对相同值调用两次
mockDelayedObs
,这是一个很难破解的问题:

因此,基本上,我创建了两个观察值:

  • 即时性是指可以立即执行的项目
  • 延迟数据项基于lastFinished$。它将发出阻止执行的最后一项
concatMap然后对这两个观测值进行合并


它的工作原理如前所述,但它并不是一个简单或直接的方法(命令式代码)。我正在关注这个讨论,寻找更优雅的解决方案。

我想你要找的操作员是
油门

这是一张工作票。实现这一点的关键是设置传递给
throttle()
的配置对象,该对象允许它在
processData()
运行期间发射(并处理)前导和尾随源排放,但忽略两者之间的任何排放

以下是Stackblitz的关键功能:

// Use 'from' to emit the above array one object at a time
const source$ = from(sourceData).pipe(

  // Simulate a delay of 'delay * delayInterval' between emissions
  concatMap(data => of(data).pipe(delay(data.delay * delayInterval))),

  // Now tap in order to show the emissions on the console.
  tap(data => console.log('next ', data.emit)),

  // Finally, throttle as long as 'processData' is handling the emission
  throttle(data => processData(data), { leading: true, trailing: true }),

).subscribe()
短而甜,除一期外,可按要求使用

更新: 上述代码的“一个问题”是,当源observable完成时,
throttle()
取消了对processData的订阅,从而有效地停止了任何需要完成的最终处理。正如巴特·范登堡(Bart van den Burg)在下面的评论中指出的那样,解决办法是使用一个主题。我认为有很多方法可以做到这一点,但是Stackblitz已经更新了以下代码,现在可以工作了:

// Set up a Subject to be the source of data so we can manually complete it
const source$ = new Subject();

// the data observable is set up just to emit as per the gist.
const dataSubscribe = from(sourceData).pipe(
    // Simulate a delay of 'delay * delayInterval' before the emission
    concatMap(data => of(data).pipe(delay(data.delay * delayInterval))),
).subscribe(data => {
    console.log('next ', data.emit); // log the emission to console
    source$.next(data); // Send this emission into the source
});

// Finally, subscribe to the source$ so we can process the data
const sourceSubscribe = source$.pipe(
    // throttle as long as 'processData' is handling the emission
    throttle(data => processData(data), { leading: true, trailing: true })
).subscribe(); // will need to manually unsubscribe later ...

为什么它不应该在
a
完成之前就跳过
c
,但是它跳过了
b
?所以基本的想法是:“将服务器与对象的这个新状态同步,但要等到上一个请求完成”。最新的始终是唯一相关的,但必须始终处理最新的,以避免丢失数据。起初,我在想exhaustMap,它确实忽略了中间事件,但随后你当然也会丢失c…虽然这不是一个定义的数字,但我的示例可以包括整个字母表,如果处理操作的数量可能在2到26之间,这取决于nexts的速度和处理时间,我不确定是否完全理解您正在尝试做什么。您能在示例中再添加一个或两个字母来确定吗?你想处理第一个可观察的对象,然后从剩下的所有对象中取第一个吗?我在postHey中添加了一个更扩展的示例,这太完美了!而且非常优雅简单,很棒。如果我将它连接到一个主题(在实际项目中就是这样),而不是一个完成的可观察对象,那么它实际上是正常工作的。看见谢谢@巴登堡-脸掌-我应该想到一个主题!我不认为你对Stackblitz的编辑“占了上风”-你可能需要把它交出来编辑你自己的副本。我编辑了Stackblitz,并将更新答案。良好的团队合作!:)+我不知道节流阀中的配置存在。然而,在发出源值而不是切换的可观测值的意义上,它的行为是否与concatMap有根本的不同?@IngoBürk-是的,这是事实,可观测链在调用
processData()
之后未被使用,因为我假设函数消耗并使用OP要查找的所有数据。但是您是对的,如果需要查看从
processData()
链下游(例如
subscribe()
内部)实际返回的值,此解决方案将无法按原样工作。@Bart-您需要
processData()返回的值吗
例如更新视图等?如果是这样,则必须使用
processData().pipe(/*执行某些操作*/)
对其进行管道传输,因为它们不是从
节流()发出的。