在前端页面中,我们经常需要重复调用同一个接口,而接口返回的数据是相同的,这时候我们就需要考虑如何通过缓存数据提高页面性能。这边文章我会总结一下我在项目中的实现方案,另外介绍一种通过ReplaySubject实现缓存的方案。

Angular提供的HTTPClient返回的Observable是Cold Observable

对Cold Observable的订阅,每一次都会创建一个新的实例,所以当我们项目中多个模块需要调用同一个接口时,就会重复发送多个相同的接口,这样对性能的影响肯定非常大。

项目中的实现方案

目前开发的是一个即时通讯项目,项目中多个模块都需要对用户数据信息进行解析,所以需要对用户数据进行缓存。

首先创建一个cache.service.ts服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { Injectable } from '@angular/core';
import { HttpClient } from "@angular/common/http";
import { BehaviorSubject, Observable } from 'rxjs';

// 封装了接口调用方法
import { ApiService } from '@core/services/api.service';

@Injectable()
export class CacheService {
private usersSubject = new BehaviorSubject([]);
public users$ = this.usersSubject.asObservable();
public get users(): any[] {
return this.usersSubject.getValue();
}
public set users(value: any[]) {
this.usersSubject.next(value);
}

constructor(private apiService: ApiService) {}

fetchUsers(): void {
// 封装的获取用户信息方法
this.apiService.retrieveAllUsers().subscribe((users) => {
this.users = users;
});
}
}

通过fetchUsers()方法获取用户信息后,赋值给this.users,使usersSubject产生新的值,组件内部只需要订阅users$就可以实现数据获取。

这里通过一个服务实现了共享数据在内存中的缓存,避免了重复多次调用同一个接口,提升了应用的性能。

如何保证用户数据的实时性?
这里的fetchUsers()方法可以帮助我们获取最新的数据。我们需要的只是一个数据更新的通知。

RelaySubject实现的缓存

ReplaySubject(size) 可以发送之前的旧值给新的订阅者,size 是定义发送具体多少个旧值给新的订阅者。
shareReplay 这个操作符会自动创建一个 ReplaySubject,一旦请求执行一次以后,就会在后续的订阅和源头 Observable 之间建立一个 ReplaySubject,ReplaySubject 是一个多播的 Hot Observable,后续订阅都是从这个中间 ReplaySubject 拿到最后一个值,从而达到缓存效果。

cache.service.ts简单修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { Injectable } from '@angular/core';
import { HttpClient } from "@angular/common/http";
import { BehaviorSubject, Observable } from 'rxjs';

// 封装了接口调用方法
import { ApiService } from '@core/services/api.service';

const CACHE_SIZE = 1;

@Injectable()
export class CacheService {
private users$: Observable<Array<any>>;

get users() {
if(!this.users$){
this.users$ = this.fetchUsers()
.pipe(
shareReplay(CACHE_SIZE)
);
}
return this.users$;
}

constructor(private apiService: ApiService) {}

fetchUsers(): void {
// 封装的获取用户信息方法
return this.apiService.retrieveAllUsers();
}
}

通过shareReplay()就将一个 cold Observable 转化为了 hot Observable,而且可以提供缓存。第一个订阅者订阅后,通过API拿到数据进行缓存,第二个订阅者订阅后,users$ 通过 ReplaySubject(1) 把最后一个旧值(缓存的数据)发送个第二个订阅者,从而实现缓存。

如何保证用户数据的实时性?
cold Obervable订阅一次后实例就失效了,所以我们没办法实时更新最新的数据,想到的方法就是刷新页面。
如果想保证数据实时刷新,我们可以把 users$ 进行一些改造,我们就使用一个简单的定时器流,来触发 users$ 产生新的数据

1
2
3
4
5
6
7
8
9
10
11
get users() {
if (!this.users$) {
const timer$ = timer(0, 10000);
this.users$ = timer$.pipe(
switchMap(() => this.fetchUsers()),
shareReplay(CACHE_SIZE)
);
}

return this.users$;
}

在对 users$ 的改造中,我们首先创建了一个定时器,用这个定时器来触发新的请求发送,switchMap()将定时器的流进行了转化,然后将请求结果发送给订阅者。这样我们就达到了更新缓存的目的。

参考:
RxJS:如何通过 RxJS 实现缓存