首页 文章

Angular 2使用observable的功能缓存http请求

提问于
浏览
5

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

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

代码:

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

可悲的是,可观察力的力量完全被忽略了 - 你手动完成所有的东西 . 实际上我不会很容易 clear the cache - 只需将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$;
    }

看起来非常棒(再次 - 没有问题 clear the cache )但我无法映射此调用的结果,即

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

这是因为底层观察者没有调用其完整方法 . 我也在这里工作 . 要实际获取数据,我必须 subscribe 到这个observable但我不想这样做:我想通过一个解析器获取我的一个组件的缓存数据,该解析器应该返回 Observable (或 Promise ),而不是 Subscription

The route

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

The Resolver

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

该组件是 never activated ,因为它的依赖关系是 never 已解析 .

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

代码:

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

这满足了我对缓存和解析的要求 BUT 我还没有找到一个干净利落的解决方案 clear the cache .

我错过了什么吗?任何人都可以想出一个更好的方法来缓存反应式观察者能够映射他们的结果,以及一旦它不再相关就刷新缓存的数据吗?

3 回答

  • 0

    这个简单的类缓存结果,因此您可以多次订阅.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);
      }
    
    }
    
  • 1

    使用选项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;
    }
    
  • 0

    我的缓存方法是将一个状态保存在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)
    

相关问题