排队可观察的请求

时间:2017-12-14 01:24:59

标签: angular rxjs observable

我有一项服务,在经过一些验证后,会调用我的后端服务器

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请求,或者我可以返回缓存数据。但是,这种逻辑似乎不符合可观察/观察者模式。我对如何解决这个问题有一些建议。

1 个答案:

答案 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仅打印一次,第一次设置缓存)。

缓存选项和系统

因为您需要systemsoptions的每个组合的新缓存条目,所以您可以使用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>

请注意,仅当使用systemsoptions的新组合时才会更新缓存。在上面的示例中,唯一键是以下字符串:

  • 'key1{"basic":true}'
  • 'key1{"basic":false}'