防止RxJS中的异步管道运算符过早完成

时间:2018-07-25 10:27:01

标签: javascript asynchronous rxjs rxjs-pipeable-operators

我正在使用RxJS 6创建pipeable operators,并且尚不清楚在操作异步时如何complete()观察者。

对于同步操作,逻辑很简单。在下面的示例中,来自源Observable的所有值都将传递到observer.next(),然后调用observer.complete()

const syncOp = () => (source) =>
  new rxjs.Observable(observer => {
    return source.subscribe({
      next: (x) => observer.next(x),
      error: (e) => observer.error(err),
      complete: () => observer.complete()
    })
  });
  
rxjs.from([1, 2, 3]).pipe(syncOp()).subscribe(x => console.log(x));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.2.2/rxjs.umd.min.js">
</script>

对于异步操作,我有点茫然。在下面的示例中,异步操作由对setTimeout()的调用表示。显然,observer.complete()在任何值传递到observer.next()之前将被称为

const asyncOp = () => (source) =>
  new rxjs.Observable(observer => {
    return source.subscribe({
      next: (x) => setTimeout(() => observer.next(x), 100),
      error: (e) => observer.error(err),
      complete: () => observer.complete()
    })
  });
  
rxjs.from([1, 2, 3]).pipe(asyncOp()).subscribe(x => console.log(x));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.2.2/rxjs.umd.min.js">
</script>

所以问题是:什么是惯用的RxJS方法来实现,以便仅在将所有值异步传递给observer.complete()之后才进行对observer.next()的调用?我应该手动跟踪未决的呼叫还是有一个更“主动”的解决方案?

(请注意,上面的示例是我的实际代码的简化,并且对setTimeout()的调用旨在表示“任何异步操作”。我正在寻找一种通用的处理方法在管道运算符中进行异步操作,而不是有关如何处理RxJS中的延迟或超时的建议。)

3 个答案:

答案 0 :(得分:3)

一种思路可能是重组您的asyncOp以使用其他操作符,例如mergeMap

这是使用这种方法重现您的示例的代码

const asyncOp = () => source => source.pipe(mergeMap(x => of(x).pipe(delay(100))));
from([1, 2, 3]).pipe(asyncOp1()).subscribe(x => console.log(x));

这是否值得考虑取决于您的asyncOp所做的事情。如果它是异步的,因为它依赖于某些回调(例如https调用或从文件系统读取的情况),那么我认为这种方法行之有效,因为您可以将基于回调的函数转换为Observable。

答案 1 :(得分:1)

仍然希望获得更多反应性/惯用的实现的输入,但是下面是我暂时决定采用的方法。

本质上,我只是使用一个计数器进行飞行中的操作(pending),并使其仅在可观察到源(completed)时才完成操作员待处理的操作(!pending)。

const asyncOp = () => (source) =>
  new rxjs.Observable(observer => {
    let pending = 0; // the number of in-flight operations
    let completed = false; // whether or not the source observable completed
    
    return source.subscribe({
      next: (x) => {
        pending++;
        
        setTimeout(() => {              
          observer.next(x);
          
          if (!--pending && completed) { // no ops pending and source completed
            observer.complete();
          }
        }, 100);
      },
      error: (e) => observer.error(err),
      complete: () => {
        completed = true;
        
        if (!pending) { // no ops pending
          observer.complete();
        }
      }
    })
  });
  
rxjs.from([1, 2, 3]).pipe(asyncOp()).subscribe(x => console.log(x));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.2.2/rxjs.umd.min.js">
</script>

答案 2 :(得分:0)

我创建了这个可运行的StackBlitz demo,以显示我认为应该做的事情。

这里的想法是使用toArray()将源中所有可观察到的值放入数组中。 toArray()之后的代码是单个值(数组)。

注意:有很多方法(操作员)可以解决问题,这只是基于我从该问题中了解的一个示例-这对RxJS Observables来说是好事,也有坏事。希望这可以帮助。 :-)

主要演示代码为:

// --- for each value, do the async service
of(...[1, 2, 3]).pipe(
  // let each value be processed by both async service...
  concatMap(no => myAsyncService$(no)),
  concatMap(no => myAsyncService2$(no)),

  // --- toArray() combines all the values (i.e. they completed)
  toArray(),

  // --- this will only be called once - with all completed values
  // --- testing: try commenting the toArray() to see the values as individual "next" value
  tap(val => {
    // see the combined values
    console.log(val)
  })
).subscribe();