避免使用RxJS5 observable进行递归

时间:2016-12-29 21:45:47

标签: recursion rxjs observable rxjs5

好的,所以我想避免使用observables的递归,使用外部和内部事件的组合,而不是回忆相同的方法/函数。

现在我有这个:

Queue.prototype.deq = function (opts) {

    opts = opts || {};

    const noOfLines = opts.noOfLines || opts.count || 1;
    const isConnect = opts.isConnect !== false;

    let $dequeue = this.init()
        .flatMap(() => {
            return acquireLock(this)
                .flatMap(obj => {
                    if(obj.error){

                    // if there is an error acquiring the lock we
                    // retry after 100 ms, which means using recursion
                    // because we call "this.deq()" again

                        return Rx.Observable.timer(100)
                            .flatMap(() => this.deq(opts));
                    }
                    else{
                        return makeGenericObservable()
                          .map(() => obj);
                    }
                })

        })
        .flatMap(obj => {
            return removeOneLine(this)
                .map(l => ({l: l, id: obj.id}))
        })
        .flatMap(obj => {
            return releaseLock(this, obj.id)
                .map(() => obj.l)
        })
        .catch(e => {
            console.error(e.stack || e);
            return releaseLock(this);
        });

    if (isConnect) {
        $dequeue = $dequeue.publish();
        $dequeue.connect();
    }

    return $dequeue;

};

而不是上面的,使用递归(希望正确),我想使用更加公平的方法。如果从acquireLock函数传回一个错误,我想每100ms重试一次,一旦成功我想停止,我不知道怎么做,我很难测试它...这是对的吗?

Queue.prototype.deq = function (opts) {

    // ....

    let $dequeue = this.init()
        .flatMap(() => {
            return acquireLock(this)
                .flatMap(obj => {
                    if(obj.error){
                        return Rx.Observable.interval(100)
                            .takeUntil(
                                acquireLock(this)
                                .filter(obj => !obj.error)
                            )
                    }
                    else{

                        // this is just an "empty" observable
                        // which immediately fires onNext()

                        return makeGenericObservable()
                              .map(() => obj);
                    }
                })

        })

     // ...

    return $dequeue;

};

有没有办法让它更简洁?我也想重试5次左右。我的主要问题是 - 我怎么能在间隔旁边创建一个计数,这样我每100毫秒重试一次,直到获得锁定或计数达到5?

我需要这样的东西:

.takeUntil(this or that)

或许我可以直接链接takeUntils,就像这样:

                   return Rx.Observable.interval(100)
                    .takeUntil(
                        acquireLock(this)
                        .filter(obj => !obj.error)
                    )
                    .takeUntil(++count < 5);

我可以这样做:

                return Rx.Observable.interval(100)
                    .takeUntil(
                        acquireLock(this)
                        .filter(obj => !obj.error)
                    )
                    .takeUntil( Rx.Observable.timer(500));

但可能正在寻找一些不那么重要的东西

但我不知道在哪里(存储/跟踪)count变量......

还希望使其更简洁,并可能检查它的正确性。

我不得不说,如果这些东西有效,它就是非常强大的编码结构。

1 个答案:

答案 0 :(得分:1)

有两个运营商可以为您提供帮助:retryretryWhen。两者都重新订阅源可观察的一个因此重试失败的操作。

检查此示例,我们的第一个count订阅的观察点失败:

let getObs = (count) => {
  return Rx.Observable.create((subs) => {
    console.log('Subscription count = ', count);

    if(count) {
      count--;
      subs.error("ERROR");
    } else {
      subs.next("SUCCESS");
      subs.complete();
    }
  
    return () => {};
  });
};

getObs(2).subscribe(console.log, console.log);
getObs(2).retry(2).subscribe(console.log, console.log);
getObs(3).retry(2).subscribe(console.log, console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.1/Rx.min.js"></script>

如你所见:

  • 如果我们按原样调用它将会失败
  • 使用retry,我们可以多次重试并获得成功回复
  • 如果观察失败太多,大部分retry将放弃并传播链中的错误。

您实际需要的是retryWhen,因为retry会立即尝试再次执行操作。

let getObs = (count) => {
  return Rx.Observable.create((subs) => {
    if(count) {
      count--;
      subs.error("ERROR");
    } else {
      subs.next("SUCCESS");
      subs.complete();
    }
  
    return () => {};
  });
};

getObs(2).retryWhen(errors => errors.delay(100))
  .subscribe(console.log, console.log);
getObs(4).retryWhen(errors => errors.delay(100))
  .subscribe(console.log, console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.1/Rx.min.js"></script>

使用retryWhen添加延迟很容易,但在尝试次数越来越难之后强制它失败:

let getObs = (count) => {
  return Rx.Observable.create((subs) => {
    if(count) {
      count--;
      subs.error("ERROR");
    } else {
      subs.next("SUCCESS");
      subs.complete();
    }
  
    return () => {};
  });
};

getObs(2)
  .retryWhen(errors => {
    return errors.delay(100).scan((errorCount, err) => {
      if(!errorCount) {
        throw err;
      }
      return --errorCount;
    }, 2);
  })
  .subscribe(console.log, console.log);

getObs(4)
  .retryWhen(errors => {
    return errors.delay(100).scan((errorCount, err) => {
      if(!errorCount) {
        throw err;
      }
      return --errorCount;
    }, 2);
  })
  .subscribe(console.log, console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.1/Rx.min.js"></script>

最后,两次重试都会引发错误,所以你需要在获取锁时执行此操作:

    .flatMap(() => {
        return acquireLock(this)
            .switchMap(obj => {
              if(obj.error) {
                return Rx.Observable.throw(obj.error);
              } else {
                Rx.Observable.of(obj);
              }
            })
            .retryWhen(...)
    })