缓存后备HTTP请求

时间:2017-11-01 00:22:12

标签: angular ionic-framework rxjs ionic3

我很难找到我开始构建的服务序列。我想要做的是从基于Promise的存储提供程序加载缓存,然后启动具有该缓存的observable。一旦HTTP请求结算,我就想用旧响应替换旧缓存。

我很难搞清楚这个过程,并希望得到任何帮助。

伪代码

import {Injectable} from "@angular/core";
import {Http} from "@angular/http";
import rxjs/add/operators/map;
import rxjs/add/operators/startWith;
import {Storage} from "@ionic/storage";

@Injectable()
export class DataService {
    private _cache = {};

    constructor(public http: Http; public storage: Storage){}

    public getCache(): Promise<any> {
        return this.storage.get('storageKey');
    }

    public setCache(val: any): Promise<any> {
        return this.storage.set('storageKey', val);
    }

    public getData(): Observable<any> {
        // Step 1: Get cache.
        this.getCache().then(cache => this._cache = cache);

        // Step 2: Make HTTP request.
        return this.http.get('endpoint')
        // Step 3: Set data mapping
           .map(res => this.normalize(res))
        // Step 4: Use resolved cache as startWith value
           .startWith(this._cache)
        // Step 5: Set cache to response body.
           .do(result => this.setCache(result).then(() => console.log('I resolved'));    
    }

    private normalize(data: Response): any {
       // Fun data-mapping stuff here
    }
}

此代码旨在在移动设备上运行,其中速度幻觉和离线功能是第一优先级。通过最初使用旧缓存设置视图,用户可以查看已存储的内容,如果HTTP请求失败(网络速度慢或没有网络),没什么大不了的,我们会回退到缓存。但是,如果用户在线,我们应该利用这个机会用来自HTTP请求的新响应替换旧缓存。

感谢您的帮助,如果我需要进一步详细说明,请告诉我。

3 个答案:

答案 0 :(得分:2)

如果我已正确理解要求,

修改 - 调整后的讨论

getData() {

  // storage is the cache
  const cached$ = Observable.fromPromise(this.storage.get('data'))  

  // return an observable with first emit of cached value
  // and second emit of fetched value.
  // Use concat operator instead of merge operator to ensure cached value is emitted first,
  // although expect storage to be faster than http 
  return cached$.concat(
    this.http.get('endpoint')
      .map(res => res.json())
      .do(result => this.storage.set('data', result))
  );
}

编辑#2 - 处理最初为空的缓存

在CodePen here中测试后,我意识到有一个边缘情况,这意味着我们应该使用merge()代替concat()。如果存储空间最初为空,concat将会阻止,因为cached$永远不会发出。

此外,需要添加distinctUntilChanged(),因为如果存储空间最初为空,merge将返回第一次提取两次。

您可以通过注释掉在CodePen中设置'prev value'的行来查看边缘大小写。

/* Solution */
const getData = () => {
  const cached$ = Observable.fromPromise(storage.get('data'))  

  return cached$ 
    .merge(
      http.get('endpoint')
        .map(res => res.json())
        .do(result => storage.set('data', result))
    )
    .distinctUntilChanged()
}
/* Test solution */
storage.set('data', 'prev value')  // initial cache value
setTimeout( () => { getData().subscribe(value => console.log('call1', value)) }, 200)
setTimeout( () => { getData().subscribe(value => console.log('call2', value)) }, 300)
setTimeout( () => { getData().subscribe(value => console.log('call3', value)) }, 400)

答案 1 :(得分:0)

使用concat确保订单?

const cache = Rx.Observable.of('cache value');
const http = function(){

  return Rx.Observable.of('http value');
                        };
var storage={}
storage.set=function(value){
 return Rx.Observable.of('set value: '+value);
}
const example = cache.concat(http().flatMap(storage.set));
const subscribe = example.subscribe(val => console.log('sub:',val));`

jsbin:http://jsbin.com/hajobezuna/edit?js,console

答案 2 :(得分:0)

取自 Richard Matsen ,这是我来到的最终结果。我添加了一个catch子句,这可能有点多余,但在网络上使用Ionic,你会得到他们的调试屏幕,这就是为什么它在那里。可以catch进行改进,以便do无法运行吗?

public getFeed(source: SocialSource): Observable<any> {
    const cached$ = Observable.fromPromise(this._storage.get(`${StorageKeys.Cache.SocialFeeds}.${source}`));
    const fetch$ = this._http.get(`${Configuration.ENDPOINT_SOCIAL}${source}`)
        .map(res => this.normalizeFacebook(res))
        .catch(e => cached$)
        .do(result => this._storage.set(`${StorageKeys.Cache.SocialFeeds}.${source}`, result));
    return cached$.concat(fetch$);
}