我正在寻找一个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);
}
问题:由于可变性和副作用,是否有办法在不诉诸dictionary
变量的情况下完成缓存多个类似的异步操作?
我已将scan
视为“收集”XHR调用结果的方法,但我不知道如何在扫描中处理异步操作。
我认为我在这里处理两个问题:一个是由事件流维护的状态管理而不是保存在变量中,第二个是在事件流中合并可能依赖于异步操作的条件操作流动。
答案 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调用返回的定义,可以将其添加到字典高速缓存中,使用匿名的,立即调用的函数。