【问题标题】:Angular 2 cache http request using the power of observablesAngular 2 使用 observables 缓存 http 请求
【发布时间】:2017-09-09 07:17:28
【问题描述】:

我发现了许多缓存响应式可观察对象的方法,更具体地说,缓存 http 请求的结果。但是,由于以下原因,我对建议的解决方案并不完全满意:

1. 这个答案https://stackoverflow.com/a/36417240/1063354 使用一个私有字段来存储第一个请求的结果,并在所有后续调用中重复使用它。

代码:

private data: Data;    
getData() {
    if(this.data) {
        return Observable.of(this.data);
    } else {
        ...
    }
}

可悲的是,observables 的力量被完全忽略了——你手动完成所有的事情。事实上,如果我对将结果分配给局部变量/字段感到满意,我不会寻找合适的解决方案。 我认为一个不好的做法的另一件重要的事情是服务不应该有状态 - 即不应该有包含数据的私有字段,这些数据会随着调用而改变。 清除缓存相当容易 - 只需将 this.data 设置为 null 即可重新执行请求。

2.这个答案https://stackoverflow.com/a/36413003/1063354建议使用ReplaySubject:

    private dataObs$ = new ReplaySubject(1);

    constructor(private http: Http) { }

    getData(forceRefresh?: boolean) {
        // If the Subject was NOT subscribed before OR if forceRefresh is requested 
        if (!this.dataObs$.observers.length || forceRefresh) {
            this.http.get('http://jsonplaceholder.typicode.com/posts/2').subscribe(
                data => this.dataObs$.next(data),
                error => {
                    this.dataObs$.error(error);
                    // Recreate the Observable as after Error we cannot emit data anymore
                    this.dataObs$ = new ReplaySubject(1);
                }
            );
        }

        return this.dataObs$;
    }

看起来非常棒(同样 - 清除缓存没问题)但我无法映射此调用的结果,即

service.getData().map(data => anotherService.processData(data))

这是因为底层观察者没有调用它的 complete 方法。我很确定很多反应式方法在这里也不起作用。要真正获取数据,我必须 订阅 这个 observable 但我不想这样做:我想通过 解析器获取我的一个组件的缓存数据> 应该返回一个Observable(或Promise),而不是一个Subscription

路线

{
    path: 'some-path',
    component: SomeComponent,
    resolve: {
      defaultData: DefaultDataResolver
    }
}

解析器

...
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Data> {
    return this.service.getData();
}

组件从不激活,因为它的依赖关系从不解决。

3.在这里https://stackoverflow.com/a/36296015/1063354我找到了一个使用publishLast().refCount()的建议

代码:

getCustomer() {
    return this.http.get('/someUrl')
        .map(res => res.json()).publishLast().refCount();
}

这满足了我对缓存解析的要求但是我还没有找到一个干净整洁的解决方案来清除缓存

我错过了什么吗?谁能想出一种更好的方法来缓存反应性可观察对象,以便能够映射其结果并在缓存数据不再相关时刷新缓存数据?

【问题讨论】:

标签: angular rxjs angular2-services


【解决方案1】:

这个简单的类会缓存结果,因此您可以多次订阅 .value 并且只发出 1 次请求。您还可以使用 .reload() 发出新请求并发布数据。

你可以像这样使用它:

let res = new RestResource(() => this.http.get('inline.bundleo.js'));

res.status.subscribe((loading)=>{
    console.log('STATUS=',loading);
});

res.value.subscribe((value) => {
  console.log('VALUE=', value);
});

来源:

export class RestResource {

  static readonly LOADING: string = 'RestResource_Loading';
  static readonly ERROR: string = 'RestResource_Error';
  static readonly IDLE: string = 'RestResource_Idle';

  public value: Observable<any>;
  public status: Observable<string>;
  private loadStatus: Observer<any>;

  private reloader: Observable<any>;
  private reloadTrigger: Observer<any>;

  constructor(requestObservableFn: () => Observable<any>) {
    this.status = Observable.create((o) => {
      this.loadStatus = o;
    });

    this.reloader = Observable.create((o: Observer<any>) => {
      this.reloadTrigger = o;
    });

    this.value = this.reloader.startWith(null).switchMap(() => {
      if (this.loadStatus) {
        this.loadStatus.next(RestResource.LOADING);
      }
      return requestObservableFn()
        .map((res) => {
          if (this.loadStatus) {
            this.loadStatus.next(RestResource.IDLE);
          }
          return res;
        }).catch((err)=>{
          if (this.loadStatus) {
            this.loadStatus.next(RestResource.ERROR);
          }
          return Observable.of(null);
        });
    }).publishReplay(1).refCount();
  }

  reload() {
    this.reloadTrigger.next(null);
  }

}

【讨论】:

    【解决方案2】:

    使用选项 3。要允许清除缓存,您可以将 observable 分配给私有成员并返回,例如。

    getCustomer() {
        if (!this._customers) {
            this._customers = this.http.get('/someUrl')
            .map(res => res.json()).publishLast().refCount();
         }
         return this._customers
    }
    
    clearCustomerCache() {
        this._customers = null;
    }
    

    【讨论】:

    • 我找到了整个方法 here 并尝试将 observable 设置为 null。不幸的是,这对我不起作用——服务继续返回之前的值,没有进行任何真正的 http 调用。您能否解释一下这些行动应该如何解决问题(想法是什么)?
    • ``` 'Observable' 类型上不存在属性 'publishLast'。``` 我该如何解决这个错误?
    • @Vivek try - import 'rxjs/add/operator/publishLast';
    【解决方案3】:

    我的缓存方法是在 reducer/scan fn 中保持状态:

    edit 3:添加了一段代码,通过键盘事件使缓存无效。

    edit 2windowWhen 运算符也适用于该任务,并允许以非常简洁的方式表达逻辑:

    const Rx = require('rxjs/Rx');
    const process = require('process');
    const stdin = process.stdin;
    
    // ceremony to have keypress events in node
    
    stdin.setRawMode(true);
    stdin.setEncoding('utf8');
    stdin.resume();
    
    // split the keypress observable into ctrl-c and c observables.
    
    const keyPressed$ = Rx.Observable.fromEvent(stdin, 'data').share();
    const ctrlCPressed$ = keyPressed$.filter(code => code === '\u0003');
    const cPressed$ = keyPressed$.filter(code => code === 'c');
    
    ctrlCPressed$.subscribe(() => process.exit());
    
    function asyncOp() {
      return Promise.resolve(Date().toString());
    }
    
    const invalidateCache$ = Rx.Observable.interval(5000).merge(cPressed$);
    const interval$ = Rx.Observable.interval(1000);
    
    interval$
      .windowWhen(() => invalidateCache$)
      .map(win => win.mergeScan((acc, value) => {
        if (acc === undefined) {
          const promise = asyncOp();
          return Rx.Observable.from(promise);
        }
    
        return Rx.Observable.of(acc);
      }, undefined))
      .mergeAll()
      .subscribe(console.log);
    

    它将仅每 5 秒执行一次异步选项,并将结果缓存在 observable 上以供其他遗漏。

    Sun Apr 16 2017 11:24:53 GMT+0200 (CEST)
    Sun Apr 16 2017 11:24:53 GMT+0200 (CEST)
    Sun Apr 16 2017 11:24:53 GMT+0200 (CEST)
    Sun Apr 16 2017 11:24:53 GMT+0200 (CEST)
    Sun Apr 16 2017 11:24:57 GMT+0200 (CEST)
    Sun Apr 16 2017 11:24:57 GMT+0200 (CEST)
    

    【讨论】:

    • 您的回答很详尽,谢谢!但是有一件事:如果我要根据超时清除缓存(据我所知),我宁愿使用publishReplay(1, 1000) 而不是publishLast(),这会使缓存每1 秒失效一次。这是你想要展示的吗?如果是,那么这不是我真正想要的 - 应该通过调用方法显式清除缓存。你能重写你的例子,以便按需清除缓存(没有超时逻辑)吗?
    • @Ultimacho 啊,我明白了。 clearCacheInterval$ 中的间隔只是一个示例,它可以与任何其他 Observable 合并/替换,例如鼠标单击。我将示例更改为在用户按下“c”时也清除缓存。
    猜你喜欢
    • 1970-01-01
    • 2017-03-24
    • 2017-10-25
    • 2017-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-29
    相关资源
    最近更新 更多