我一直在寻找一种在Angular中缓存HTTP数据的方法,最后我决定使用.shareReplay()
来实现它。此外,我希望手动清除缓存或在ReplaySubject
过期时清除缓存,因为我知道我可以设置.shareReplay()
的持续时间。满足上述两个要求的最佳做法是什么?
答案 0 :(得分:1)
您应该使用startsWith
,因为这允许您使用本地缓存初始化数据,然后使用更新的数据替换。
答案 1 :(得分:1)
您不能真正使用shareReplay
来缓存可观察的刷新缓存并同时提供初始缓存种子。
shareReplay
可以使用ReplaySubject
来缓存值ReplaySubject
startWith
来提供初始值,但是存在在您只期望一个值的情况下发出多个值的问题。我认为这足以与自定义运算符和缓存控制器配合使用。
const { defer } = require('rxjs/observable/defer');
const { AsyncSubject, Scheduler } = require('rxjs');
class Cache {
get isExpired() {
return this.scheduler.now() > this.expiration;
}
constructor(initialValue, maxTime = Number.POSITIVE_INFINITY, scheduler = Scheduler.queue) {
this.isComplete = false;
this.hasError = false;
this.subject = null;
this.maxTime = maxTime;
this.scheduler = scheduler;
this.expiration = Number.POSITIVE_INFINITY;
if (typeof initialValue !== 'undefined') {
this.subject = new AsyncSubject();
this.subject.next(initialValue);
this.subject.complete();
this.isComplete = true;
this.extendExpiration();
}
}
extendExpiration() {
this.expiration = this.scheduler.now() + this.maxTime;
}
preventExpiration() {
this.expiration = Number.POSITIVE_INFINITY;
}
flush() {
this.expiration = 0;
}
}
function cachingOperator(cache = new Cache()) {
let refCount = 0;
let subscription;
return function cacheOperation(source) {
refCount++;
if (!cache.subject || cache.hasError || cache.isExpired) {
cache.hasError = false;
cache.preventExpiration(); // prevent expiration before underlying observable completes
cache.subject = new AsyncSubject();
subscription = source.subscribe({
next(value) { cache.subject.next(value); },
error(err) {
cache.hasError = true;
cache.subject.error(err);
},
complete() {
cache.isComplete = true;
cache.extendExpiration();
cache.subject.complete();
},
});
}
const innerSub = cache.subject.subscribe(this);
return () => {
refCount--;
innerSub.unsubscribe();
if (subscription && refCount === 0 && cache.isComplete) {
subscription.unsubscribe();
}
};
};
}
const caching = cache => source => source.lift(cachingOperator(cache));
Cache
对象将保持状态。您的代码负责实例化,因此可以完全控制。
用法:
const myCache = new Cache({ result: 'initialValue' }, 1000); // initial value and cache time 1s
const request = httpService.get(url)
.pipe(
caching(myCache)
);
request.subscribe(); // initial value
setTimeout(() => {
request.subscribe(); // issues new request
request.subscribe(); // cache request
myCache.flush();
request.subscribe(); // new request
}, 1100);
绝对可以改进API,使其更适合您的用例,但是我希望这为如何实现提供了粗略的想法。
答案 2 :(得分:0)
在寻找类似问题的解决方案时看到了这个问题。我无法使其仅与shareReplay api一起使用,但想共享一个仅限rxjs的解决方案(tw:inTypescript),以防有人使用它:
private cache = {};
private cacheProperty(key:string, prop:string, httpCall$:Observable<any>, expirationTime?: number): Observable<any>{
if(!(key in this.cache && prop in this.cache[key])){
const timer$ = timer(expirationTime)
const clearCache = new Subject<void>()
const clearCache$ = merge(timer$, clearCache.asObservable()).pipe(take(1))
const cacheProp$ = httpCall$.pipe(shareReplay(1, 5000),takeUntil(clearCache$))
clearCache$.subscribe(_=> delete this.cache[key][prop])
this.cache[key] = Object.assign(!!this.cache[key] ? this.cache[key] : {} ,{[prop]:{call$:cacheProp$, clear:clearCache }})
}
return this.cache[key][prop].call
}
public clearPropCache(key:string, prop:string){
this.cache[key][prop].clear.next()
}
对于我们正在进行的项目,它是个性化的,它需要多个“用户”缓存(由键拆分)以及道具表示的多种类型的缓存(例如,bio)。您可以输入到期时间,也可以在第二个时间以空白主题手动清除它。
我们也使用了angular,所以httpCall $应该以从HttpClient模块返回的可观察形式出现,而没有在其他来源尝试过。
我还发现本文对于创建和缩小我的解决方案非常有帮助:https://blog.thoughtram.io/angular/2018/03/05/advanced-caching-with-rxjs.html
希望它可以帮助其他使用简单缓存解决方案的人!
答案 3 :(得分:0)
要与es6结合使用的节点版本,请切换到import
而不是require
语句。
在工厂,您可以用http请求替换Observable of(v)
。
我发布信息的可能性更大,而不是分享最先进的解决方案。
const {of} = require('rxjs');
const {shareReplay, tap} = require('rxjs/operators');
const BUFFER_SIZE = 1;
const CACHE_EXPIRATION = 10; // in ms
const factory = v => of(v).pipe(
tap(v => console.log(v, 'pre processing')),
shareReplay(BUFFER_SIZE, CACHE_EXPIRATION),
);
const cache = {};
const get = id => {
let guard = false;
if (cache.hasOwnProperty(id)) {
cache[id].subscribe(v => {
console.log(id, 'not expired');
guard = true;
});
if (guard) {
return cache[id];
}
console.log(id, 'expired');
}
console.log(id, 'creting new observble pipeline');
cache[id] = factory(id);
return cache[id];
};
get('1').subscribe(v => console.log(v, 'finall sub'));
setTimeout(() => get('1').subscribe(v => console.log(v, 'finall sub')), 5);
setTimeout(() => get('1').subscribe(v => console.log(v, 'finall sub')), 8);
输出:
1 creting new observble pipeline
1 pre processing
1 finall sub
1 not expired
1 finall sub
1 expired
1 creting new observble pipeline
1 pre processing
1 finall sub