2017-04-13 52 views
4

我发现了许多方法来缓存被动观察对象,更具体地说,是http请求的结果。不过,我并不完全满意的,因为以下原因提出的解决方案:Angular 2使用可观察函数的功能缓存http请求

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

代码:

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

可悲的是,观测的力量完全被忽略 - 你手动做所有的东西。事实上,如果我满意将结果分配给本地变量/字段,我不会寻找适当的解决方案。 另一个我认为不好的做法的重要事情是服务不应该有一个状态 - 即应该没有包含从呼叫到呼叫改变的数据的专用字段。 清除缓存很容易 - 只需将this.data设置为空,并且请求将被重新执行。

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)) 

发生这种情况的原因是,底层观察者未调用其完成方法。我很确定,很多被动方法在这里也不适用。为了实际获得数据,我必须订阅这个可观察的,但我不想这样做:我想通过解析器得到我的一个组件的缓存数据,它应该返回一个Observable(或无极),而不是一个认购

路线

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

解析器

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

的组件是从未启用,因为它的依赖是从未解决。

这里https://stackoverflow.com/a/36296015/1063354我发现使用publishLast()引用计数()的建议。

代码:

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

这满足了我的要求,两个缓存解决我还没有找到一个干净整洁的解决清除缓存

我错过了什么吗?任何人都可以想出一种更好的方法来缓存被动的可观察对象,这些对象可以映射他们的结果,以及一旦不再相关时就刷新缓存的数据?

+0

的可能的复制[什么是分享RxJs 5的角2 HTTP网络调用的结果的正确方法?](https://stackoverflow.com/questions/36271899/what-is-the-correct-way-to-share-the-result-of-an-angular-2-http-network-call-in) –

回答

0

使用选项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

我发现这整个方法[这里](http://www.syntaxsuccess.com/viewarticle/caching-with-rxjs-observables-in-angular-2.0),并尝试将observable设置为null。不幸的是,这并不适合我 - 服务继续返回以前的值,而不是进行任何真正的http调用。 你能解释一下这些行为应该如何解决这个问题(这个想法是什么)? – Ultimacho

+0

'''Property'publishLast'不存在'Observable '类型。'''我怎样才能解决这个错误? – Vivek

+1

@Vivek try - import'rxjs/add/operator/publishLast'; – glendaviesnz

0

我到高速缓存方法将被保持的状态在减速机/扫描FN:

编辑3:添加一段代码无效的键盘事件缓存。

编辑2:该windowWhen操作也适用于任务,并允许表达逻辑在一个相当简洁的方式:

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); 

它将执行async选项只有每5秒和缓存结果对可观察的其他遗漏。

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) 
+0

您的回答非常详尽,谢谢!但有一件事:如果我要根据超时清除缓存(据我所知),我宁愿使用'publishReplay(1,1000)'而不是'publishLast()',这会使缓存每失效1次第二。这是你打算展示的吗?如果是,那么它不是我想要的 - 缓存应该通过调用方法明确地清除。 您可以重写您的示例,以便按需清除缓存(没有超时逻辑)? – Ultimacho

+0

@Ultimacho啊,我明白了。'clearCacheInterval $'中的时间间隔就是一个例子,它可以被任何其他Observable合并/替换,例如鼠标点击。我改变了这个例子,当用户按下'c'时也清除缓存。 – mkulke

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); 
    } 

}