RxJS:缓存几个没有可变性和副作用的XHR调用

时间:2016-05-12 17:16:39

标签: javascript rxjs

我正在寻找一个RxJS示例,如何缓存一系列XHR调用(或其他异步操作),因此不必重复相同的调用,同时尊重不变性并且没有副作用。

这是一个简单的,可变的例子:

var dictionary = {}; // mutable

var click$ = Rx.Observable.fromEvent(document.querySelector('button'), 'click', function (evt) {
  return Math.floor(Math.random() * 6) + 1; // click -> random number 1-6 (key)
})
  .flatMap(getDefinition);

var clicksub = click$.subscribe(function (key) {
  console.log(key);
});

function getDefinition (key) {
  if ( dictionary[key] ) { // check dict. for key
    console.log('from dictionary');
    return Rx.Observable.return(dictionary[key]);
  }
  // key not found, mock up async operation, to be replaced with XHR
  return Rx.Observable.fromCallback(function (key, cb) {
    dictionary[key] = key; // side effect
    cb(dictionary[key); // return definition
  })(key);
}

JSBin Demo

问题:由于可变性和副作用,是否有办法在不诉诸dictionary变量的情况下完成缓存多个类似的异步操作?

我已将scan视为“收集”XHR调用结果的方法,但我不知道如何在扫描中处理异步操作。

我认为我在这里处理两个问题:一个是由事件流维护的状态管理而不是保存在变量中,第二个是在事件流中合并可能依赖于异步操作的条件操作流动。

2 个答案:

答案 0 :(得分:2)

使用与上一个问题(RxJS wait until promise resolved)相同的技术,您可以使用scan并将http调用添加为您要跟踪的状态的一部分。这是未经测试的,但您应该能够轻松地将其调整为测试:

restCalls$ = click$
  .scan(function (state, request){
    var cache = state.cache;
    if (cache.has(request)) {
      return {cache : cache, restCallOrCachedValue$ : Rx.Observable.return(cache.get(request))}
    }
    else {
      return {
        cache : cache,
        restCallOrCachedValue$ : Rx.Observable
          .defer(function(){
            return Rx.Observable
              .fromPromise(executeRestCall(request))
              .do(function(value){cache.add(request,value)})
          })
      }
    }
  }, {cache : new Cache(), restCallOrCachedValue$ : undefined})
  .pluck('restCallOrCachedValue$')
  .concatAll()

所以基本上你传递一个observable来调用流,或者直接返回包含在observable中的缓存中的值。在这两种情况下,相关的可观察量将由concatAll按顺序打开。请注意,只有在订阅时才使用cold observable来启动http调用(假设executeRestCall执行调用并立即返回一个promise)。

答案 1 :(得分:0)

使用@ user3743222的响应,此代码会缓存XHR调用:

// set up Dictionary object
function Dictionary () { this.dictionary = {} }
Dictionary.prototype.has = function (key) { return this.dictionary[key] }
Dictionary.prototype.add = function (key, value) { this.dictionary[key] = value }
Dictionary.prototype.get = function (key) { return this.dictionary[key] }

var definitions$ = click$

  .scan(function (state, key) {
    var dictionary = state.dictionary

    // check for key in dict.
    if ( dictionary.has(key) ) {
      console.log('from dictionary')
      return {
        dictionary: dictionary, 
        def$: Rx.Observable.return(dictionary.get(key))
      }
    }

    // key not found
    else {
      return {
        dictionary: dictionary, 
        def$: Rx.Observable.fromPromise(XHRPromise(key))
          .do(function (definition) { dictionary.add(key, definition) })
      }
    }

  }, {dictionary : new Dictionary(), def$ : undefined})

  .pluck('def$') // pull out definition stream
  .concatAll()   // flatten

更新:在// key not found块中,执行传递key的函数XHRPromise(返回一个promise),并将结果传递给RxJS方法fromPromise。接下来,链接一个do方法,在该方法中抓取定义并将其添加到dictionary缓存中。

跟进问题:看来我们已经删除了副作用问题,但是当将定义和关键字添加到字典中时,这仍然被认为是变异的吗?

将此文件用于存档目的,第一次迭代使用此代码,完成相同的缓存过程:

    // ALTERNATIVE key not found
    else {
      var retdef = Rx.Observable.fromPromise(function () {
        var prom = XHRPromise(key).then(function (dd) {
          dictionary.add(key, dd) // add key/def to dict.
          return dd
        })
        return prom
      }()) // immediately invoked anon. func.
      return { dictionary: dictionary, def$: retdef }
    }

这里,使用返回promise的函数XHRPromise。但是,在将该承诺返回到事件流之前,请拉出一个then方法,在该方法中抓取承诺的定义并将其添加到字典缓存中。之后,promise对象通过RxJS方法fromPromise返回到事件流。

要访问从XHR调用返回的定义,可以将其添加到字典高速缓存中,使用匿名的,立即调用的函数。