我有一项服务,在经过一些验证后,会调用我的后端服务器
get(systems?: string[], options?: {}): Observable<System[]> {
if (
// cache check
) {
// validate options
return this.api.get('/systems', options)
.map(response => {
// proccess results, store to cache
});
} else {
return Observable.of(this.systems);
}
}
初始化服务时调用此请求,以获取基本数据(传递选项{basic: true}
。在我的一个页面上,在组件初始化时调用相同的服务,没有基本选项。但是,当该网站被加载到该页面,当然两个HTTP请求都会激活,因为它们都没有返回并填充缓存。
我似乎无法弄清楚如何最好地避免这种情况。在理想的解决方案中,请求将排队,我将使用缓存逻辑来确定是否需要另一个HTTP请求,或者我可以返回缓存数据。但是,这种逻辑似乎不符合可观察/观察者模式。我对如何解决这个问题有一些建议。
答案 0 :(得分:1)
为了避免竞争条件,您需要缓存Observable而不是直接缓存值。
直接缓存值的问题是您可以对您的服务进行多次连接调用,并且您的服务将检查缓存中的值,这些值在您的请求完成之前将不存在(这会创建竞争条件 - - 您的请求与检查缓存的逻辑竞争。您的服务最终会发出多个请求并多次覆盖相同值的缓存。
通过缓存Observable,您可以在创建后立即将它们放在缓存中,并在任何订阅者需要它时返回该Observable(无论Observable是否已解析为某个值),因此您不会正在发出多个请求。
为了简化操作,我们可以缓存ReplaySubject
个对象,这些对象将保留使用next
调用的最后一个值,并在新订阅者subscribe
时自动返回该值。这本质上是一个主题,它缓存以前的值并将它们返回给新订阅者。
请参阅此简单示例,其中cache
是表示ReplaySubject
s缓存的对象:
let cache = {};
getCached('key1').subscribe(val => console.log("Sub1: ", val)); // first subscriber
getCached('key1').subscribe(val => console.log("Sub2: ", val)); // second subscriber immediately after first
function getCached(key) {
if(cache[key]) return cache[key]; // does cached Observable exist? If so, return it right away
console.log("Not cached. Fetching new value.");
cache[key] = new Rx.ReplaySubject(1); // set cache equal to new ReplaySubject
setTimeout(() => cache[key].next(500), 1000); // fetch new value and alert our ReplaySubject subscribers
return cache[key]; // return ReplaySubject immediately
}
<script src="https://unpkg.com/@reactivex/rxjs@5.0.0-rc.4/dist/global/Rx.js"></script>
关于上面示例的关键注意事项是getCached('key1')
在缓存值有机会调用next
(延迟500毫秒)之前背靠背调用, ReplaySubject
仅缓存一次(注意console.log
仅打印一次,第一次设置缓存)。
缓存选项和系统
因为您需要systems
和options
的每个组合的新缓存条目,所以您可以使用systems
+ options
的字符串表示设置您的密钥JSON.stringify
上的options
:
let cache = {};
getCached('key1', {basic: true}).subscribe(val => console.log("Sub1: ", val)); // first subscriber
getCached('key1', {basic: true}).subscribe(val => console.log("Sub2: ", val)); // second subscriber immediately after first
getCached('key1', {basic: false}).subscribe(val => console.log("Sub3: ", val));
function getCached(systems, options) {
const key = systems + JSON.stringify(options);
console.log("key is: ", key);
if(cache[key]) return cache[key]; // does cached Observable exist? If so, return it right away
console.log("Not cached. Fetching new value.");
cache[key] = new Rx.ReplaySubject(1); // set cache equal to new ReplaySubject
setTimeout(() => cache[key].next(500), 1000); // fetch new value and alert our ReplaySubject subscribers
return cache[key]; // return ReplaySubject immediately
}
<script src="https://unpkg.com/@reactivex/rxjs@5.0.0-rc.4/dist/global/Rx.js"></script>
请注意,仅当使用systems
和options
的新组合时才会更新缓存。在上面的示例中,唯一键是以下字符串:
'key1{"basic":true}'
'key1{"basic":false}'