RxJS之使用ReplaySubject 实现高级缓存

321 阅读4分钟

前言

在开发 Web 项目时,性能始终都是一个重点。要想提升 Angular 应用的速度,我们可以做一些工作,比如要摇树优化 (tree-shaking)、AoT (ahead-of-time)、模块的懒加载以及缓存等等...

而缓存是提升网站用户体验的最有效的方式之一,尤其是当用户使用宽带受限的设备或网络环境较差。缓存数据或资源的方式有很多种。静态资源通常都是由标准的浏览器缓存或 Service Workers 来进行缓存。Service Workers 所做的api缓存更多的是对图像、HTML、JS 或 CSS 文件等资源的缓存.

就缓存更新应用机制而言,在我们的实际业务场景中,或主动推送,或被动轮询等等,但不管何种自定机制,都是基于业务可行性能保证的情况下,同时提升应用的响应能力、减少网络花销,并有内容在网络中断时可用等等更良好的用户体验.

缓存实现方式

缓存的实现方式有多种, 这里介绍一种使用ReplaySubject实现api缓存,提高性能的方式:

ReplaySubject 是一个多播的 Hot Observable , 而且但是不仅是发送‘当前值’,还可以是之前的旧值,并且可以设置保存的旧值得数量。

在Angular 中通过 HttpClient 执行 Http Request 返回的 Observables 是 Cold Observable。

两者的具体区别分析可以查看下看看Cold vs Hot Observables HttpClient Observable 每次被订阅都需要调用 http request,

  1. 对于同样的API 返回同样的值,不同页面重复调用会浪费 http 资源降低性能。
  2. 做缓存处理,也能更好的增加用户体验

需求场景

在业务中,例如进入某分类中进入消息列表或者新闻列表或拿省市区信息等等,更倾向先取缓存数据,然后通过换成更新机制去自动或者有好提示更新数据,这里以进消息列表为例.

具体实现

  1. 添加一个服务:baseService 在其中编写调用接口方法

    import { map } from 'rxjs/operators';
    
    @Injectable()
    export class JokeService {
    
      constructor(private http: HttpClient) { }
    
      private requestList() {
    	return this.http.get<Array<Infor>>(API_URL).pipe(
    	  map(response => response),
    	  catchError(error => {
    		  console.log("something went wrong " + error)
    		  return of([]);
    	  })
    	);
      }
    }
    
  2. 在上面的baseService中暴露一个list属性,上面介绍过 Http Request他返回的 是 Cold Observable, 是包含笑话列表的 Observable ,这意味着为每次订户都会重新发出整个数据流,从而导致多次的 HTTP 请求,这里简单的返回this.requestList(),肯定无法实现我们想要的效果,毕竟,缓存的理念是提升应用的加载速度并将网络请求的数量限制到最小。所以我们需要用到一个操作符: shareReplay shareReplay 这个操作符会自动创建一个 ReplaySubject,一旦 http request 执行一次以后,就会在后续的 订阅和源头 Observable之间建立一个 ReplaySubject,ReplaySubject 是一个多播的 Hot Observable,后 续订阅都是从这个中间 ReplaySubject 拿到最后一个值,从而达到缓存效果。

// 缓存的数量
const CACHE_SIZE = 1;

// 共享的实例保存在私有属性 cache$ 中
 private cache$: Observable<Array<Infor>>;

  get list() {
    if (!this.cache$) {
      this.cache$ = this.requestJokes().pipe(
        shareReplay(1)
      );
    }
    return this.cache$;
  }
  1. 在组件中使用
@Component({
  ...
})
export class ListComponent implements OnInit {
  list$: Observable<Array<Info>>;
  constructor(private baseService: BaseService) { }
  ngOnInit() {
    this.list$ = this.jokeService.list;
  }
  ...
}
<div *ngFor="let item of list$ | async">...</div>

以上就简单的完成了数据缓存了,初次调用完成从0到1的获取数据,而后被缓存下来,同时也从单播变成多播,

从而不管是再次进入这里,还是其他页面需要通过这个接口获取数据都可以直接取缓存list这个缓存里的数据了,

是不是非常简单,不过在实际业务场景中,当然需要考虑数据更新以及同步的问题,例如像消息列表,当然是一进去就会再调用接口去做数据更新,自动替换老数据

还有些业务场景,比如你看一篇文章看到一会儿,数据突然就被刷掉了,那就有点闹心了,这时候你可以写个弹窗组件提示用户文章已更新,是否更新的确认弹窗等等....

总之使用ReplaySubject实现api缓存可以在很多地方提升用户体验感,是个非常不错的东西