暂停和恢复可观察的流,请提出更好的选择

时间:2017-06-06 10:52:13

标签: angular typescript rxjs5

我需要从一个可观察的项目中听取一组项目。当某些条件出现时,将对该项执行异步任务,并且该组件将“忙”,直到完成为止。我想暂停处理订阅中的项目,直到此任务完成(因为以下项目的处理取决于结果),然后从序列中的下一个项目继续而不会有任何损失。

下一部分可能是最好的阅读,同时看看Plunk here

为实现这一目标,我使用bufferswtichMap。我认为这些可以自己完成这项工作,但是switchMap会破坏并重新创建每次重置序列的订阅。

export class AppComponent implements OnInit {
    source$: Observable<any>;
    clearBuffer$ = new Subject();
    busy$ = new Subject();

    private itemSubscription: Subscription;
    private stayAliveSubscription: Subscription;

    items: any[] = [];

    constructor() { }

    ngOnInit() {
      this.source$ = Observable.range(1, 500).zip(
        Observable.interval(500),
        function (x, y) { return x; }
      ).share();

      this.busy$
        .subscribe(result => {
          if (!result) {
            this.clearBuffer$.next();
          }
        }, error => {
          console.log(error);
        });
    }

    start() {
      if (!this.itemSubscription) {
        this.itemSubscription =
          this.busy$.switchMap(busy => {
            if (busy) {
              return this.source$.buffer(this.clearBuffer$);
            } else {
              return this.source$;
            }
          })
            .subscribe(items => {
              if (Array.isArray(items)) {
                this.items.push('buffered: ' + items.join());
              } else {
                this.items.push('live feed: ' + items);
              }
            }, error => {
              this.items.push(error);
            });

        this.stayAliveSubscription = this.source$
          .subscribe(result => {
            console.log(result);
          }, error => {
            console.log(error);
          });

        this.busy$.next(false);
      }
   }
...
}

要解决此问题,现在共享源$ observable,并启动单独的订阅( stayAliveSubscription ),以便始终使用单个订阅。这对我来说似乎很混乱,我想问一下是否有人可以向我展示更好/替代方法来解决这个问题。

我将工作样本放入Plunk here点击开始以启动订阅,然后将忙碌切换设置/取消设置为缓冲并继续。

编辑:使用concatMap工作代码

我更改了Plunk以使用concatMap。我也粘贴了下面的代码。关键是concatMap中返回的busy observable必须完成,你不能多次返回busy $ observable,并在忙状态改变时调用next。

    source$: Observable<any>;
    busy$ = new Subject();
    busy: boolean;

    private itemSubscription: Subscription;
    private stayAliveSubscription: Subscription;

    items: any[] = [];

    constructor() { }

    ngOnInit() {
      this.source$ = Observable.range(1, 500).zip(
        Observable.interval(500),
        function (x, y) { return x; }
      );

      this.busy$
        .subscribe(busy => {
          this.busy = <any>busy;
        });
    }

    start() {
      if (!this.itemSubscription) {
        this.itemSubscription = this.source$.concatMap(item => {
          const busySubject = new Subject();
          this.busy$
            .subscribe(result => {
              busySubject.next(item);
              busySubject.complete();
            });

          if (this.busy) {
            return busySubject;
          } else {
            return Observable.of(item);
          }

        })
          .subscribe(item => {
            this.items.push(item);
          }, error => {
            this.items.push(error);
          });
      }

      this.setBusy(false);
    }

2 个答案:

答案 0 :(得分:2)

我不完全明白你要做什么,但是如果只是在&#34;异步任务&#34;中保留发出值的顺序的问题。可能需要很长(随机)的时间,我想你可以使用concatMap运算符。

理论

  

concatMap

     

将每个源值投影到Observable,Observable在输出Observable中合并,以序列化方式等待每个源在合并下一个之前完成。   concatMap operator marble diagram

实践

在此示例中,src Observable每隔100ms发出一次值,每个值都映射到一个新的observable,该observable发出0到2000ms之间的值(异步任务)。您可以看到订单是安全的。

&#13;
&#13;
let src = Rx.Observable.timer(0,100);
src.concatMap(i=>{
  return Rx.Observable.timer(Math.random()*2000).mapTo(i); // this is the async task
}).subscribe(data=>console.log(data));
&#13;
<script src="https://unpkg.com/rxjs@5.4.0/bundles/Rx.min.js"></script>
&#13;
&#13;
&#13;

制作热门观察

您也不应该使用这些订阅来生成可观察的发射数据。实际上,您应该使用.publish().connect()代替share()subscribe()转换cold observable to a hot one

this.source$ = Observable.range(1, 500).zip(
        Observable.interval(500),
        function (x, y) { return x; }
      ).publish();
// blah blah blah some code
this.source$.connect();

答案 1 :(得分:1)

delayWhen是非常强大的运算符。我的解决方案使用mergeMapdelayWhen

功能:重试,限制,暂停,恢复

  1. 创建并订阅Observable
const concurrentLimit = 5
const retryLimit = 10
const source$ = from(new Array(100).fill(0).map((_, i) => i))
// remove <boolean> if not typescript
const pause$ = new BehaviorSubject<boolean>(false);
const pass$ = pause$.pipe(filter((v) => !v));

const throttledTask$ = source$.pipe(
  mergeMap((item) => {
    return of(item).pipe(
      delayWhen(() => pass$),
      mergeMap(async (item) => {
         // you can also throw some errors
         return await new Promise((resolve)=>
             setTimeout(resolve(item), Math.random()*1000))
      }),
      retryWhen((errors$) => errors$.pipe(delay(1000), take(retryLimit)))
    );
  }, concurrentLimit)

const subscription = throttledTask$.subscribe(x => console.log(x))
  1. 添加暂停/继续事件处理程序
const pause = () => { pause$.next(true) }
const resume = () => { pause$.next(false) }

说明:

  1. delayWhen将暂停流,并等待直到pass$信号发出。
  2. BehaviorSubject用于构成pass$信号,订阅时将发出最后一个值。
  3. mergeMap可以处理异步任务,并具有并发线程数限制参数。当delayWhen暂停流时,该流将保留在mergeMap内并占用并发的“线程”。
  4. retryWhen将重新订阅,直到errors$.pipe(delay(1000), take(retryLimit))发出完整或错误消息为止。