2017-04-13 29 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); 
    } 

}