如何将一个可观察量变成一个可观察到的具有特定值的长轮询可观察量?

时间:2015-09-11 13:24:32

标签: javascript long-polling rxjs

我正在使用RxJs创建一个交互式网页。

这就是我想要实现的目标:

我有一个生成令牌的应用程序。这些令牌可以被外部实体使用。

当用户创建令牌时,页面开始轮询Web服务器的状态(是否消耗)。使用令牌时,页面会刷新。

因此,当创建令牌时,会每2秒向服务器发送一个请求,询问该令牌是否已被使用。

我有Observable个字符串代表我的generatedTokens

我实际上已经有了一个使用Rx.Scheduler.default类的工作实现,它允许我手动完成任务。但是,我不禁觉得应该有一个更简单,更优雅的解决方案。

这是当前的代码:

class TokenStore {
  constructor(tokenService, scheduler) {
    // actual implementation omitted for clarity
    this.generatedTokens = Rx.Observable.just(["token1", "token2"]);

    this.consumedTokens = this.generatedTokens
      .flatMap(token => 
        Rx.Observable.create(function(observer) {
          var notify = function() {
            observer.onNext(token);
            observer.onCompleted();
          };
          var poll = function() {
            scheduler.scheduleWithRelative(2000, function() {
                // tokenService.isTokenConsumed returns a promise that resolves with a boolean
                tokenService.isTokenConsumed(token)
                  .then(isConsumed => isConsumed ? notify() : poll());
              }
            );
          };
          poll();
        }));
  }
}

是否有类似“repeatUntil”的方法?我正在寻找一个与上面代码完全相同的实现,但看起来更像是这样:

class TokenStore {
  constructor(tokenService, scheduler) {
    // actual implementation omitted for clarity
    this.generatedTokens = Rx.Observable.just(["token1", "token2"]);

    this.consumedTokens = this.generatedTokens
      .flatMap(token =>
        Rx.Observable.fromPromise(tokenService.isTokenConsumed(token))
                     .delay(2000, scheduler)
                      // is this possible?
                     .repeatUntil(isConsumed => isConsumed === true));
  }
} 

2 个答案:

答案 0 :(得分:3)

有趣的是,在发布问题几分钟之后,答案让我感到震惊。我认为橡胶涂层可能不会那么愚蠢。

无论如何,答案包括两部分:

  • 使用repeat()filter()first()

  • 的组合可以实现repeatUntil
  • fromPromise有一些内部延迟缓存机制,导致后续订阅不会触发新的AJAX请求。因此,我不得不使用Rx.Observable.create

解决方案:

class TokenStore {
  constructor(tokenService, scheduler) {
    // actual implementation omitted for clarity
    this.generatedTokens = Rx.Observable.just(["token1", "token2"]);

    this.consumedTokens = this.generatedTokens
      .flatMap(token =>
        // must use defer otherwise it doesnt retrigger call upon subscription
        Rx.Observable
        .defer(() => tokenService.isTokenConsumed(token))
        .delay(2000, scheduler)
        .repeat()
        .filter(isConsumed => isConsumed === true)
        .first())
    .share();
  }
} 

次要的旁注:"分享()"确保两个observable都很热,这避免了每个订阅者都会导致ajax请求开始触发的情况。

答案 1 :(得分:0)

class TokenSource {
  constructor(tokenService, scheduler) {
    this.generatedTokens = Rx.Observable.just(["token1", "token2"]).share();

    this.consumedTokens = this.generatedTokens
      .flatMap(token => 
         Rx.Observable.interval(2000, scheduler)
               .flatMap(Rx.Observable.defer(() => 
                          tokenService.isTokenConsumed(token)))
               .first(isConsumed => isConsumed === true))
      .share()

  }
}

您可以利用两个事实:

  1. flatMap有一个重载,它带有一个可观察的信息,每次新事件发生时都会被重新订阅

  2. defer可以使用返回promise的方法。该方法将在每次订阅时重新执行,这意味着您不必滚动自己的Promise - > Observable转化。