【问题标题】:Cache Fallback HTTP Requests缓存回退 HTTP 请求
【发布时间】:2017-11-01 00:22:12
【问题描述】:

我在确定我开始构建的服务的顺序时遇到了很多麻烦。我想做的是从基于 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 请求的新响应。

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

【问题讨论】:

  • 这里的类型没有意义。既然你startWith(cache),那么cache 必须与你的结果JSON 承诺流的类型相同。但是,它也会缓存 resolved JSON 对象,除非cacheres.json() 都是Promise&lt;Promise&lt;...,否则这是不可能的。
  • 我有一个映射函数,而不是.map(res =&gt; res.json()。让我更新一下。

标签: angular ionic-framework rxjs ionic3


【解决方案1】:

如果我正确理解了要求,

编辑 - 讨论后调整

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)

【讨论】:

  • 这会等到承诺解决后再继续吗?
  • @EHorodyski 您是否尝试将 HTTP 请求延迟到缓存的承诺解决后?如果调用 getData 的次数远远超过请求完成的次数,会发生什么情况?
  • @concat -- 对不起,我昨晚不知所措,可能解释得不够好。我正在尝试将第一个流对象设置为来自缓存承诺的内容,然后执行 HTTP 请求。这是在手机上,所以我希望先加载缓存以显示用户,然后发出请求,然后替换缓存。如果 HTTP 调用失败,它就会失败,这没关系,因为有某种缓存(或空对象)来自设备存储。
  • 由于问题有一个 Angular 标签,我假设 http.get 返回一个可观察的而不是承诺。 storage.get 是一个承诺,所以添加了转换。 @EHorodyski,是的,主要的 observable 只会发出解析的值。
  • @EHorodyski,顺便说一句,设计不错。
【解决方案2】:

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

【讨论】:

  • 问题是存储服务是基于承诺的,而不是基于 Observable 的。这就是让我大失所望的原因。
  • 使用 observable.fromPromise(yourPromise)
【解决方案3】:

取自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$);
}

【讨论】:

    猜你喜欢
    • 2016-10-16
    • 2019-11-08
    • 2012-04-05
    • 2016-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-08
    • 2018-09-15
    相关资源
    最近更新 更多